# EvoHub Docs > Guides and reference for EvoHub: on-call, uptime monitoring, status pages, retros, boards, docs sites, changelogs, billing and the API. Every page of EvoHub Docs, as Markdown. Index: https://docs.evohub.io/llms.txt --- Source: https://docs.evohub.io/introduction.md # EvoHub documentation EvoHub brings the operational side of running software into one place: being paged when something breaks, knowing when a service is down, telling customers about it, and improving afterwards. Everything lives in one organization, you invite your whole team for free, and you pay only for what you use. These guides explain how each part of EvoHub works and how to set it up. ## Start here :::cards{cols=2} ::card{title="Quickstart" icon="rocket" href="/quickstart"} Your first alert on your phone in about five minutes. ::card{title="What is EvoHub" icon="compass" href="/what-is-evohub"} The products, the console and the mobile app, and how they fit together. ::card{title="Create your account" icon="user" href="/create-your-account"} Sign up, verify your email and set up your first organization. ::card{title="Organizations and teams" icon="users" href="/organizations-and-teams"} Invite people, create teams and switch between organizations. ::card{title="Roles and permissions" icon="shield" href="/roles-and-permissions"} Decide who can see, change and delete what. ::card{title="Your home page" icon="home" href="/your-home-page"} Pick your widgets, shortcuts and accent colour. ::card{title="Get help" icon="life-buoy" href="/get-help"} Open a support ticket or report a bug from the console. ::card{title="Setup, the Guide and tours" icon="compass" href="/setup-tours-and-tips"} The first setup, the Guide that takes you to your first results, and product tours. ::: ## Products :::cards{cols=3} ::card{title="On-Call" icon="bell" href="/on-call-overview"} Schedules, escalation policies, alerts and incidents, with voice, push and email notifications. ::card{title="Integrations" icon="plug" href="/integrations-overview"} 44 sources, from Prometheus, Grafana and Datadog to email and any Events API v2 sender. ::card{title="Status Pages" icon="globe" href="/status-pages-overview"} Branded public status pages on your own domain. ::card{title="Uptime" icon="zap" href="/uptime-overview"} HTTP, keyword, TCP, ping and heartbeat monitors. ::card{title="Retro and Board" icon="layers" href="/retro-boards"} Retrospectives and Kanban boards for your team. ::card{title="Docs" icon="book-open" href="/docs-overview"} Product and API documentation on your own domain. ::card{title="Changelog" icon="flag" href="/changelog-overview"} Release notes with a What's new widget, feeds and email updates. ::card{title="Catalog" icon="package" href="/catalog-overview"} Services and their owners beside what actually runs: drift, the service map and SLA reports. ::card{title="EvoHub Agent" icon="server" href="/agent-overview"} The read-only agent for Kubernetes clusters and Linux VMs that reports to Catalog. ::card{title="EvoTrail" icon="chart" href="/evotrail-overview"} Every product's numbers, usage, PDF reports and the audit trail. ::card{title="Billing" icon="credit-card" href="/how-billing-works"} Usage-based pricing in EvoHub tokens, with a free monthly allowance. ::card{title="API" icon="code" href="/api-overview"} Automate EvoHub with API keys and scopes. ::: ## Help :::cards{cols=3} ::card{title="FAQ" icon="message" href="/faq"} Short answers to common questions. ::card{title="Troubleshooting" icon="wrench" href="/troubleshooting"} Missing notifications, alerts that did not open, domains that do not verify. ::card{title="Limits" icon="info" href="/limits"} Rate limits, sizes and how long data is kept. ::: If something in these guides is unclear or missing, open a ticket from **Help & Support** in the console (see [Get help](https://docs.evohub.io/get-help.md)) or write to **info@evosync.io**. You can also use the feedback buttons at the bottom of every page to tell us what to improve. --- Source: https://docs.evohub.io/quickstart.md # Quickstart This guide takes you from a new account to an alert ringing on your phone in about five minutes. You need an EvoHub account (see [Create your account](https://docs.evohub.io/create-your-account.md)), your phone, and a terminal with `curl` for the test alert. Nothing here costs money: it stays well inside the free monthly allowance. :::steps ### Install the app and sign in Install EvoHub from the [App Store](https://apps.apple.com/us/app/evohub/id6799893875) or [Google Play](https://play.google.com/store/apps/details?id=io.evohub), sign in with the same account you use in the console, and allow notifications. Your phone is now registered for push. ### Turn on your notifications In the console, open the avatar menu and go to **My Account → Notifications**. Check that **Mobile Push** is on and says **Sends to your mobile devices**. For phone calls as well, verify your number first: **Phone number not verified** links to where you do it. A new account can also do this in the setup flow's **Phone number** step, which calls you to confirm the number (see [Setup, tours and tips](https://docs.evohub.io/setup-tours-and-tips.md)). Then turn **Phone Call** on. ### Create an escalation policy that pages you Go to **On-Call → Escalation Policies** and choose **New Policy**. Enter a **Policy Name**, for example `Quickstart`. Under **Escalation Steps**, leave step 1 at **Notify after** **0 (now)**, set its target to **User** and pick yourself. Choose **Create Policy**. ### Create an integration Go to **On-Call → Integrations**, open **+ Add Integration** and choose **Webhook**. Enter an **Integration Name**, pick the **Quickstart** policy as the **Escalation Policy**, and choose **Create Integration**. Copy the **Webhook URL** it shows. ### Send a test alert Run this in a terminal, with your webhook URL in place of the one shown: ```bash curl -X POST 'https://evohub.io/ingest/alerts?key=YOUR_INTEGRATION_KEY' \ -H 'Content-Type: application/json' \ -d '{"title": "Quickstart test alert", "severity": "high", "fingerprint": "quickstart-test"}' ``` EvoHub answers with `"created": 1`, and within seconds your phone shows a push notification (and rings, if you turned on phone calls). ### Acknowledge it Tap the notification to open the alert in the app and choose **Acknowledge**, or, on a call, press **4**. The escalation stops. The alert's timeline in **On-Call → Alerts** shows every notification and who acknowledged it. ### Resolve it Send the same fingerprint with `"status": "resolved"`, the way a monitoring tool reports a recovery: ```bash curl -X POST 'https://evohub.io/ingest/alerts?key=YOUR_INTEGRATION_KEY' \ -H 'Content-Type: application/json' \ -d '{"fingerprint": "quickstart-test", "status": "resolved"}' ``` Or choose **Resolve** on the alert in the app or the console. ::: If nothing arrives, see [Troubleshooting](https://docs.evohub.io/troubleshooting.md#i-didn-t-get-a-notification). ## Next steps You now have the whole path working: a tool sends to an integration, the integration's escalation policy pages a person, and the person responds. To make it real for a team: :::cards{cols=2} ::card{title="Connect your monitoring tool" icon="plug" href="/integrations-overview"} Point Prometheus, Grafana, Datadog, Zabbix or any of 44 integrations at EvoHub instead of `curl`. ::card{title="Set up a schedule" icon="calendar" href="/schedules-and-rotations"} Rotate who is on call, and make the policy's first step target the schedule instead of you. ::card{title="Add a backup step" icon="bell" href="/escalation-policies"} Page someone else if the first person does not acknowledge. ::card{title="Invite your team" icon="users" href="/organizations-and-teams"} Inviting is free; EvoHub never charges per member. ::: Moving from Opsgenie or PagerDuty? The importers create schedules, escalation policies and integrations for you: see [Migrate from Opsgenie](https://docs.evohub.io/migrate-from-opsgenie.md) and [Migrate from PagerDuty](https://docs.evohub.io/migrate-from-pagerduty.md). ## Related - [On-Call overview](https://docs.evohub.io/on-call-overview.md) - [Notifications](https://docs.evohub.io/notifications.md) - [Alert API and generic webhook](https://docs.evohub.io/generic-webhook.md) - [Mobile app](https://docs.evohub.io/mobile-app.md) --- Source: https://docs.evohub.io/what-is-evohub.md # What is EvoHub EvoHub brings the tools an engineering team uses around operations and communication into one place: paging and on-call, uptime monitoring, public status pages, retrospectives, task boards, documentation sites and changelogs. This page explains what each product does, how the console is organized, and how you pay for it. ## The products | Product | What it is for | | --- | --- | | **On-Call** | Receive alerts from your monitoring tools, route them through escalation policies, and page the person on call by voice call, mobile push or email. Includes schedules, rotations, overrides, incidents and maintenance windows. | | **Uptime** | Monitor websites, APIs and hosts with HTTP, keyword, TCP and ping checks, and watch scheduled jobs with heartbeat monitors. A failing monitor can page On-Call. | | **Status Pages** | Publish a public status page with components, incidents and scheduled maintenance, on your own domain, with email and feed subscriptions. | | **Retro** | Run team retrospectives on a live board with cards, votes, comments and action items. | | **Board** | Track work on Kanban-style boards with lists and cards, and an optional review gate. | | **Docs** | Write and publish documentation sites like this one, with review, GitHub sync and a custom domain. | | **Changelog** | Publish product updates as a changelog with an embeddable widget, feeds and email subscribers. | | **Catalog** | Declare your services, their owners and tiers, and see them beside what a read-only agent or cloud account finds running: unowned workloads, drift between environments, a service map and monthly SLA reports. | | **EvoTrail** | Read every product's numbers for a period you pick, see how each product is used, download PDF reports, and search the organization's audit trail. | All products share one account, one organization, one set of members and teams, and one bill. ## The EvoHub console You use EvoHub through the console at [https://evohub.io](https://evohub.io). Sign in at [https://evohub.io/login](https://evohub.io/login). - The **home page** shows numbers, shortcuts, your services and recent activity, and you can arrange it the way you like. See [Your home page](https://docs.evohub.io/your-home-page.md). - The top bar holds the **Services** menu (On-Call, Uptime, Status Pages, Retro, Board, Docs, Changelog and EvoTrail), **Search**, the **Guide** (the next step on your path, and every tour), and the **organization switcher**, which lists every organization you belong to. - Each product has its own left-hand navigation. - Your avatar menu in the top bar has: - **My Account**: **Profile** (your details, phone number, password and the organizations you belong to), **Notifications**, **Security** (two-factor authentication and connected Google or GitHub accounts), **Active Sessions**, **API keys** and **Preferences** (product tours and tips). - **Organization**: **Members** and **Teams**, and **Invitations** for people who may invite. Depending on your permissions you also see **Organization settings**, **Chat apps** (Slack and Microsoft Teams, connected once for every product), **Roles** and **Organization Keys**; administrators also see **Login Policies**. The audit trail is in EvoTrail, under **Audit Logs**. - **Usage & Billing**: what your organization has used this month and what it will cost. - **Help & Support**: open a ticket with the EvoHub team. See [Get help](https://docs.evohub.io/get-help.md). Product tours are started, replayed or turned off from the **Guide** in the top bar, or **My Account → Preferences**. See [Setup, the Guide and tours](https://docs.evohub.io/setup-tours-and-tips.md). What you see depends on your role. A link to a page you have no permission for is hidden, and the API refuses the request. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). ## Organizations and teams Everything you create in EvoHub belongs to an **organization**. When you sign up, EvoHub creates a personal organization for you, and you can create more or be invited into others. Inside an organization, **teams** group people so that schedules, monitors, boards and retros can be scoped to them and roles can be granted to them. See [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md). ## Mobile apps The EvoHub mobile app is an on-call companion for iOS and Android. Use it to receive push notifications for alerts, acknowledge and resolve them, take over a shift, and see who is on call. Configuration, such as schedules, escalation policies and integrations, is done in the web console. - App Store: [EvoHub](https://apps.apple.com/us/app/evohub/id6799893875) - Google Play: [EvoHub](https://play.google.com/store/apps/details?id=io.evohub) ## Usage-based billing EvoHub does not charge per seat. Inviting your whole company costs nothing. You pay for what actually happens, such as voice calls placed, alerts received, uptime checks run and custom domains served. Usage is measured in **EvoHub tokens (EHU)**. Every organization gets a free monthly allowance of EHU, and most small teams stay within or close to it. You only pay for usage above the allowance, and only after you add a card or buy credit. See [How billing works](https://docs.evohub.io/how-billing-works.md) and the [pricing page](https://evohub.io/pricing). ## The API Most things you can do in the console you can also do through the REST API at `https://evohub.io/api/v1`, authenticated with an API key. See the [API overview](https://docs.evohub.io/api-overview.md). ## Related - [Create your account](https://docs.evohub.io/create-your-account.md) - [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md) - [Your home page](https://docs.evohub.io/your-home-page.md) - [How billing works](https://docs.evohub.io/how-billing-works.md) - [On-Call overview](https://docs.evohub.io/on-call-overview.md) - [EvoTrail overview](https://docs.evohub.io/evotrail-overview.md) --- Source: https://docs.evohub.io/create-your-account.md # Create your account This page walks you through signing up for EvoHub, verifying your email address and signing in for the first time. Signing up is free and does not ask for a credit card. ## Sign up Go to [https://evohub.io/register](https://evohub.io/register). You can sign up in two ways. :::tabs ::tab{title="Google or GitHub"} Select **Continue with Google** or **Continue with GitHub** and approve access with that provider. EvoHub creates your account from the name and email address the provider shares, and signs you in. ::tab{title="Email and password"} Fill in your name, email address and a password of at least 8 characters, then select **Create account**. EvoHub sends a verification link to your email address. See [Verify your email address](#verify-your-email-address). ::: By signing up you accept the [Terms of Service](https://evohub.io/terms) and the [Privacy Policy](https://evohub.io/privacy). > [!NOTE] > Each email address can hold one EvoHub account. If you see "An account with this email already exists", sign in instead, or reset your password from the sign-in page. ## Verify your email address If you signed up with email and password, you must verify your address before you can sign in. :::steps ### Open the verification email Look for the email from EvoHub and select the verification link. The link is valid for 24 hours. ### Sign in After the link confirms your address, sign in at [https://evohub.io/login](https://evohub.io/login). ::: If the email does not arrive, try to sign in: the sign-in page offers **Resend verification email**. If the link has expired, the page it opens offers **Resend verification** for your email address. Check your spam folder too. ## Sign in Sign in at [https://evohub.io/login](https://evohub.io/login) with **Continue with Google**, **Continue with GitHub**, or your email and password. - If you turned on [two-factor authentication](https://docs.evohub.io/two-factor-authentication.md), EvoHub asks for the 6-digit code from your authenticator app after your password. - If your organization requires two-factor authentication and you have not set it up, EvoHub walks you through setting it up before you continue. - After 5 failed password attempts, the account is locked for 15 minutes. The lock applies even to the correct password, so wait it out or reset your password. ## Forgot your password On the sign-in page, select **Forgot password?**, enter your email address and select **Send reset link**. The link in the email lets you set a new password and is valid for 15 minutes. If you signed up with Google or GitHub and never set a password, you can create one later from **My Account → Profile** (**Create Password**). ## Your first organization When you sign up, EvoHub creates a personal organization for you, named after you (for example, "Ada Lovelace's Organization"), and makes you its **Owner**. You can start using every product in it right away. - To rename it, open **Organization → Organization settings** and change **Organization Name**. - To create another organization, for example one for your company, open the organization switcher in the top bar and choose **New Organization**. - If a colleague invited you, accept the invitation to join their organization. See [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md#join-through-an-invitation). > [!TIP] > Your personal organization cannot be deleted. Create a separate organization for your company so that you can manage and, if needed, delete it independently. ## Next steps A new account starts with a short, optional setup: your phone number, how you want to be paged and two-factor sign-in. See [Setup, tours and tips](https://docs.evohub.io/setup-tours-and-tips.md). :::cards{cols=2} ::card{title="Quickstart" icon="rocket" href="/quickstart"} Get your first alert on your phone in about five minutes. ::card{title="Invite your team" icon="users" href="/organizations-and-teams"} Bring colleagues into your organization and group them into teams. ::card{title="Set up roles" icon="shield" href="/roles-and-permissions"} Decide who can change what. ::card{title="Turn on two-factor authentication" icon="lock" href="/two-factor-authentication"} Protect your account with an authenticator app. ::card{title="Start with On-Call" icon="bell" href="/on-call-overview"} Connect a monitoring tool and get paged. ::: ## Related - [What is EvoHub](https://docs.evohub.io/what-is-evohub.md) - [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md) - [Two-factor authentication](https://docs.evohub.io/two-factor-authentication.md) --- Source: https://docs.evohub.io/organizations-and-teams.md # Organizations and teams An organization is the workspace everything in EvoHub belongs to: alerts, schedules, monitors, status pages, boards, docs sites, changelogs, members and the bill. Teams are groups of people inside an organization. This page explains how to work with both. ## Organizations Every organization is separate from every other. Data created in one organization is never visible from another, and each organization has its own members, roles, teams, API keys and billing. You can belong to several organizations, with a different role in each: - **Your personal organization** is created when you sign up. You are its Owner, and it cannot be deleted. - **Organizations you create.** Open the organization switcher in the top bar, choose **New Organization**, enter an **Organization name** and select **Create**. You become its Owner. One account can own up to 5 organizations. - **Organizations you are invited to.** See [Join through an invitation](#join-through-an-invitation). **My Account → Profile** lists every organization you belong to and your role in each. ## Switch organizations Open the organization switcher in the top bar and pick an organization. The console reloads in that organization: every product, member list and bill you see is now that organization's. If the organization you switch to requires two-factor authentication and you have not set it up, EvoHub asks you to set it up first. See [Two-factor authentication](https://docs.evohub.io/two-factor-authentication.md). ## Rename an organization Open **Organization → Organization settings**, change **Organization Name** and select **Save**. This needs the **Organization settings** write permission, which Owners and Admins have. ## Time zone Every time in the console — alerts, incidents, maintenance, audit logs, invoices — is shown on your organization's clock, the same for everyone in it, and every date and time you pick is read on it. In an incident, "it went down at 14:05" means the same minute to every responder. The date picker shows the clock's offset beside the time, for example **GMT+3**. - The setup asks for it the first time someone who may change organization settings signs in, prefilled with their browser's time zone. - Change it in **Organization → Organization settings → Default time zone**. This needs the **Organization settings** write permission. - Not set, times are shown on each person's browser clock. Some things keep a clock of their own: an on-call schedule uses its [schedule timezone](https://docs.evohub.io/schedules-and-rotations.md) (a new schedule starts on the organization's), your notification schedule uses the time zone you set on it, and a public status page shows times in each visitor's own time zone. Analytics ranges are whole days in UTC. ## Invite people Inviting is free: EvoHub never charges per member. :::steps ### Open the invite dialog Go to **Organization → Members** and select **Invite Member**. ### Enter the email address Type the address of the person you want to invite. ### Choose what they can do Pick one or more roles, and optionally one or more teams. A team brings the roles that team holds. You can only give roles whose permissions you hold yourself. If you cannot see roles, you choose between **Member** and **Viewer** (and **Admin**, if you are an administrator). ### Send the invitation EvoHub emails an invitation link. The person holds the roles and teams you picked from the moment they accept. ::: Inviting needs the **Members → Invite** permission. Owners and Admins have it; the default Member role does not. To let others invite, give them a custom role with that permission. ### Manage pending invitations **Organization → Invitations** lists pending invitations. From there you can resend an invitation email or cancel an invitation. An invitation is valid for 7 days; after that, send a new one. ### Join through an invitation Open the link in the invitation email. - **If you already have an EvoHub account**, sign in with the email address the invitation was sent to and accept. An invitation can only be accepted by the account with that exact email address. - **If you are new to EvoHub**, create your account from the invitation page. Because the invitation already proved you own the email address, you do not need to verify it again. After you accept, the organization appears in your organization switcher. ## Remove a member Go to **Organization → Members**, select **Remove** on the member's row and confirm. They lose access to the organization immediately. Removing members needs the **Members → Write** permission, which Owners and Admins have. The Owner cannot be removed, and you cannot remove yourself this way. A removed member's personal API keys for that organization stop working at the same time. See [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md). ## Teams A team is a named group of people in an organization. Teams do two things: - **Scope work.** Schedules, monitors, boards and retros can belong to a team. Work that belongs to a team is visible to that team's members; work that belongs to no team is visible across the organization. Owners and Admins see every team's work. - **Carry roles.** Give a team a role and everyone in the team holds that role for as long as they are in it. Remove someone from the team and they lose it. ### Create a team :::steps ### Open Teams Go to **Organization → Teams** and select **Create Team**. ### Name it Enter a **Name** and, optionally, a description of what the team owns. ### Add people and roles Open the team, add people from your organization, and give the team a role if its members should gain permissions through it. ::: Creating and editing teams needs the **Teams → Write** permission, which the default Member and Admin roles include. Everyone can see the teams they are in, even without permission to read all teams. > [!TIP] > A team that holds no role gives its members nothing extra. That is fine when you only use the team to scope work; give it a role when membership should also grant permissions. ## Delete an organization Only the Owner can delete an organization, and a personal organization cannot be deleted. :::steps ### Open Organization settings Go to **Organization → Organization settings**. The **Danger Zone** section is visible to the Owner. ### Confirm Select **Delete Organization**, type the organization's name, and select **Delete**. ::: > [!DANGER] > Deleting an organization is permanent. Its members lose access immediately, and its data in every product (On-Call, Uptime, status pages, retros, boards, docs sites and changelogs) is removed. This cannot be undone. Billing records that make up invoices already issued are kept, as described in [Privacy and your data](https://docs.evohub.io/privacy-and-data.md). ## Related - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) - [Create your account](https://docs.evohub.io/create-your-account.md) - [Privacy and your data](https://docs.evohub.io/privacy-and-data.md) --- Source: https://docs.evohub.io/roles-and-permissions.md # Roles and permissions Access in EvoHub is decided by organization roles. A role is a named set of permissions, and you give roles to people or to teams. This page explains the default roles, how to build your own, and the full list of permissions per product. ## How access works - Every permission check is made by EvoHub's servers, not only by the console. Hiding a button is a convenience; the API refuses the same action. - A refused action returns **403 Forbidden**. You stay signed in; you are simply not allowed to do that. See [Errors](https://docs.evohub.io/errors.md). - What a person can do is the sum of every role they hold, directly or through their teams. - Roles belong to the organization. The same person can be an Admin in one organization and a Viewer in another. - Docs sites and changelogs have no roles of their own. Access to them comes from organization roles like everything else. ## Owner and the default roles Every organization starts with three default roles. They are marked **(default)** in role pickers. | Role | What it is for | | --- | --- | | **Owner** | The person who created the organization. Passes every check, and is the only one who can delete the organization. | | **Admin** | Runs the organization. A person who holds Admin passes every check, except deleting the organization. | | **Member** | The day-to-day work across every product: respond to alerts, manage schedules and monitors, run status pages, retros, boards, docs and changelogs. Reads everything else. | | **Viewer** | Read-only across every product. Sees everything, changes nothing. | What the default roles allow, product by product: | | Viewer | Member | Admin | | --- | --- | --- | --- | | Read everything (including billing usage and statements, and EvoTrail's numbers) | Yes | Yes | Yes | | Read the audit trail in EvoTrail's **Audit Logs** | — | Yes | Yes | | Respond to alerts, edit schedules, escalation policies, maintenance, postmortems | — | Yes | Yes | | Manage On-Call integrations and voice and update templates | — | Yes | Yes | | Create and edit monitors, silences and notification channels | — | Yes | Yes | | Create and edit status pages, components, incidents, maintenance and subscribers | — | Yes | Yes | | Create and run retros, boards, docs sites and changelogs; write, publish and approve | — | Yes | Yes | | Manage teams | — | Yes | Yes | | Invite people | — | — | Yes | | Edit or remove members | — | — | Yes | | Delete a retro, archive a board, delete a docs site or a changelog | — | — | Yes | | Read the Board, Docs and Changelog audit trails | — | — | Yes | | Declare and change Catalog services and tiers | — | Yes | Yes | | Decide Catalog matches, manage environments, enroll agents, connect cloud accounts | — | — | Yes | | Change org settings, roles, organization keys, login policies, billing (cards, credit, commitment) | — | — | Yes | > [!NOTE] > Deleting whole things that a team works in, such as a board, a retro, a docs site or a changelog, is Admin-only by default. Members can create, rename and configure them, but not make them disappear. You can hand this permission to someone deliberately with a custom role. Administrators can change what the Member and Viewer roles (and the Admin role) grant; everyone holding the role gets the change. Default roles cannot be renamed or deleted. > [!INFO] > When the Admin role is given to a **team** rather than to a person, it grants its listed permissions: everything a Member can do plus managing people and the delete permissions above. It does not grant editing roles, issuing organization keys, changing org settings or changing billing. Only a person whose own role is Admin passes every check. ## Custom roles Build a custom role when the default roles are too broad or too narrow, for example "Can respond to alerts but not edit schedules" or "Billing only". :::steps ### Open Roles Go to **Organization → Roles** and select **Create Role**. ### Name and describe it Enter a **Name** and a **Description** that says what the role is for. ### Choose permissions Start from a ready-made level per product (see below), or under **Or start from a default role** pick Admin, Member or Viewer to copy its permissions, then adjust individual boxes. Use the search box to find a permission, or **Everything** to select all you are allowed to grant. ### Save and assign Save the role. Then give it to people (open a member in **Organization → Members** and add the role) or to a team (open the team in **Organization → Teams** and give it the role). ::: Rules that apply to every role: - **You can only grant what you hold.** Permissions you do not hold yourself are greyed out, and the server refuses a role that would give someone more than you have. Only an administrator can give someone the Admin role. - **A write brings its read.** Selecting a write permission also selects the read it needs, so a role never ends up able to edit something it cannot open. - **Creating and editing roles** needs the **Roles → Write** permission. Owners and Admins have it; you can give it to others with a custom role. ### Ready-made roles The role editor offers ready-made levels for each product. Clicking one fills in its permissions; every box stays editable afterwards. | Product | Levels | | --- | --- | | On-Call | **Stakeholder** (sees incidents and maintenance), **Observer** (sees everything, touches nothing), **Responder** (adds acknowledging and resolving), **Manager** (everything, including integrations, settings and the audit log) | | Uptime | **Viewer**, **Operator** (adds editing and silencing monitors), **Manager** (adds notification channels and the audit log) | | Status Pages | **Viewer**, **Incident Manager** (posts incidents and maintenance, sets component status), **Page Manager** (everything, including domains, design and subscribers) | | Board | **Uses boards**, **Creates boards** (adds creating boards, settings, visibility and the review gate), **Manages boards** (adds archiving) | | Docs | **Docs writer** (drafts and change requests), **Docs reviewer** (approves and publishes), **Manages docs sites** (adds site settings, domain and deleting sites) | | Changelog | **Changelog writer**, **Changelog reviewer**, **Manages changelogs** | | Retro | **Takes part in retros**, **Runs retros** (adds creating, shaping and sharing retros), **Manages retros** (adds deleting) | | Organization | **Directory Viewer**, **Inviter**, **Organization Manager** (members, teams, roles, org settings, organization keys, audit log) | | Billing | **Billing Admin** (usage, statements, payment methods, billing profile, credit and commitment) | | Catalog | **Viewer**, **Service Owner** (adds declaring services: owner, tier, runbook and RACI), **Catalog Admin** (everything, including matches, environments, agents and cloud accounts) | ## Permissions reference Permissions are grouped by product, as in the role editor. Each has an identifier, which you also see on API keys. | Product | Resource | Permissions | | --- | --- | --- | | Organization | Members | `identity:user:read`, `identity:user:write`, `identity:user:invite` | | | Teams | `identity:team:read`, `identity:team:write` | | | Roles | `identity:role:read`, `identity:role:write` | | | Organization settings | `identity:org:write` | | | Organization Keys | `identity:apikey:read`, `identity:apikey:write` | | | Agents | `identity:agent:read`, `identity:agent:write` (create enrollment tokens, rename and revoke agents) | | | Cloud accounts | `identity:cloudaccount:read`, `identity:cloudaccount:write` (connect, change and disconnect) | | | Audit Log | `identity:audit:read` | | On-Call | Alerts | `oncall:alert:read`, `oncall:alert:write`, `oncall:alert:respond` (acknowledge, resolve, snooze, take over, assign) | | | Incidents | `oncall:incident:read`, `oncall:incident:write` | | | Schedules | `oncall:schedule:read`, `oncall:schedule:write` | | | Escalation Policies | `oncall:escalation:read`, `oncall:escalation:write` | | | Integrations | `oncall:integration:read`, `oncall:integration:write` | | | Maintenance Windows | `oncall:maintenance:read`, `oncall:maintenance:write` | | | Postmortems | `oncall:postmortem:read`, `oncall:postmortem:write` | | | Voice & templates | `oncall:settings:write` | | | Audit Log | `oncall:audit:read` | | Uptime | Monitors | `uptime:monitor:read`, `uptime:monitor:write` | | | Incidents | `uptime:incident:read` | | | Silences | `uptime:silence:read`, `uptime:silence:write` | | | Notification Channels | `uptime:channel:read`, `uptime:channel:write` | | | Audit Log | `uptime:audit:read` | | Status Pages | Pages | `status:page:read`, `status:page:write` | | | Components | `status:component:read`, `status:component:write` | | | Incidents | `status:incident:read`, `status:incident:write` | | | Maintenance | `status:maintenance:read`, `status:maintenance:write` | | | Subscribers | `status:subscriber:read`, `status:subscriber:write` | | | Audit Log | `status:audit:read` | | Retro | Boards | `retro:board:read`, `retro:board:write`, `retro:board:delete` | | | Cards | `retro:card:read`, `retro:card:write` | | | Action Items | `retro:action:read`, `retro:action:write` | | | Audit Log | `retro:audit:read` | | Board | Boards | `board:board:read`, `board:board:write`, `board:board:delete` (archive) | | | Lists | `board:list:read`, `board:list:write` | | | Cards | `board:card:read`, `board:card:write` | | | Board Members | `board:member:write` | | | Review Gate | `board:review:write` | | | Audit Log | `board:audit:read` | | Docs | Sites | `docs:site:read`, `docs:site:write`, `docs:site:delete` | | | Pages | `docs:page:write`, `docs:page:publish` (publish and approve) | | | Audit Log | `docs:audit:read` | | Changelog | Sites | `changelog:site:read`, `changelog:site:write`, `changelog:site:delete` | | | Entries | `changelog:entry:write`, `changelog:entry:publish` (publish and approve) | | | Audit Log | `changelog:audit:read` | | Billing | Usage | `billing:usage:read` | | | Statements | `billing:invoice:read` | | | Payment Methods | `billing:payment:read`, `billing:payment:write` | | | Billing Profile | `billing:profile:read`, `billing:profile:write` | | | Credit | `billing:credit:write` | | | Commitment | `billing:commitment:write` | | Support | Audit Log | `support:audit:read` | | EvoTrail | Metrics | `evotrail:metrics:read` (the numbers and usage views; each product also needs its own read permission) | | | Audit Log | `evotrail:audit:read` (the organization-wide audit log; each row also needs its product's audit permission) | | | Settings | `evotrail:settings:write` | | Catalog | Services | `catalog:component:read`, `catalog:component:write` (services, their RACI and SLA, and tiers) | | | Resources | `catalog:resource:read`, `catalog:resource:write` (decide which service a resource belongs to) | | | Environments | `catalog:source:read`, `catalog:source:write` (environments, and pausing agents and cloud accounts) | A few things need no permission at all: every member can see who else is in the organization, see the teams they belong to, open support tickets, and manage their own profile, notifications, sessions, two-factor authentication and personal API keys. ## Related - [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md) - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) - [Errors](https://docs.evohub.io/errors.md) --- Source: https://docs.evohub.io/your-home-page.md # Your home page The home page is the console's start page; the EvoHub logo in the top bar takes you back to it from anywhere. It shows a greeting, a row of numbers, shortcuts, your services and what happened recently. You can change all of it: which widgets show, in what order and how wide, which shortcuts sit in **Quick actions**, and the console's accent colour. Your home page is yours alone. It is saved for you in each organization separately and follows you to any browser you sign in on. Nobody else's page changes when you change yours. ## The default page Until you customize it, the home page shows: - **Greeting**, with today's date. - Five numbers: **Members**, **Uptime** (monitors up out of all active ones), **Open tickets** (your support tickets awaiting an answer), **Incidents** (not yet resolved) and **On-Call** (who is on call now). - **Quick actions**, with seven shortcuts: **Declare incident**, **Add monitor**, **Take over on-call**, **Who's on call**, **Post status update**, **Schedule maintenance** and **Invite member**. - **Your Services**: a card for every EvoHub product. - **Recent activity** across the organization. - **Platform health**: EvoHub's own status. A widget or shortcut needs the same permission as the screen it comes from. If your role cannot read monitors, for example, the **Uptime** number is not shown; if it cannot invite people, **Invite member** is left out. ## Customize it :::steps ### Open Customize Select the **Customize home** button (the sliders icon) in the bottom-left corner of the home page. The **Customize** panel opens beside the page. ### Change what you like Everything you change shows on the page at once, as a draft. See the sections below. ### Save Select **Save**. **Cancel** closes the panel and throws the draft away. ::: **Reset to default** puts back the default page and accent after you confirm **Back to the default page?** ### Widgets Under **Home widgets**, tick a widget to show it and untick it to hide it. Drag a widget, or use its arrows, to change the order. A widget your role cannot read says **You don't have access to this** and cannot be shown. Open a widget's settings to change, where it applies: - **Width on wide screens**: **1 of 3**, **2 of 3** or **Full**. - **Rows to show**: for lists, from 1 to 20 (5 by default). - **Accent**: a colour for that widget alone. While Customize is open you can also work on the page itself: drag a widget by its handle to move it, drag its edge to make it wider or narrower, and hide it from its toolbar. These widgets are available, besides the default ones: | Widget | What it shows | | --- | --- | | **Open alerts**, **Monitors down**, **Status page incidents**, **Maintenance today**, **Cards awaiting review**, **Open retro actions**, **Reader feedback**, **Docs change requests**, **Subscribers**, **Usage this month** | One number each, in the row of numbers. | | **My open alerts** | Alerts and incidents assigned to you, most severe first. | | **My on-call** | Whether you are on call, your next shift and who takes over. | | **Awaiting my approval** | Board cards, docs change requests and changelog entries waiting on you. | | **Monitors needing attention** | Monitors down or degraded now, since when, and the open incident. | | **Status pages** | What each page shows now, with any live incident or maintenance. | | **On-call now** | Who covers each team's schedules, and when they hand off. | | **Open alerts** | Triggered and acknowledged alerts, live. | | **Recent incidents** | The latest incidents. | | **My board cards** | Cards assigned to you. | | **Latest changelog** | Recently published entries. | | **My retro actions** | Retro action items assigned to you, overdue first. | | **Upcoming maintenance** | Maintenance in the next 7 days. | | **Reader feedback** | Unresolved feedback on your docs sites and changelogs. | | **Waiting support tickets** | Your tickets to EvoHub awaiting an answer, oldest first. | | **Usage this month** | Spend so far, credit left and the month-end estimate. | ### Quick actions Open the **Quick actions** widget's settings. **Shown, in this order** lists your shortcuts: reorder them with the arrows or remove one. Add more under **Add an action** and **Add a place to go**. Remove them all and the row is hidden. | Kind | Shortcuts | | --- | --- | | Actions — each opens its form | **Declare incident**, **Add monitor**, **Take over on-call**, **Schedule maintenance**, **Post status update**, **New changelog entry**, **New docs page**, **New board card**, **Add integration**, **Import from Opsgenie**, **Import from PagerDuty**, **New status page**, **New docs site**, **New changelog site**, **New board**, **Start a retro**, **Invite member**, **Create API key** | | Places to go | **EvoTrail**, **Audit Logs**, **Members**, **Organization settings**, **Chat apps**, **On-Call**, **My open alerts**, **Who's on call**, **Uptime**, **Status Pages**, **Retro**, **Board**, **Docs**, **Changelog**, **Usage & billing** | A shortcut that needs to know "which one" first — **Take over on-call** (which schedule), **Post status update** (which open incident), **New changelog entry**, **New docs page** and **New board card** (which changelog, site or board) — asks with a short list. If there is only one, it goes straight there. Only shortcuts you are allowed to use are offered. ### Your Services Open the **Your Services** widget's settings to tick the products you want to see, reorder them with the arrows, and turn **Show planned services** off to hide products that are not out yet. ### Accent colour Under **Appearance**, pick the console's accent colour: **Blue** (the default), **Indigo**, **Violet**, **Pink**, **Red**, **Orange**, **Green**, **Teal** or **Slate**. It colours buttons, links and highlights across the whole console, not just the home page. Each product's own colour stays as it is. ## Search **Search** in the top bar (or **⌘K**, **Ctrl+K** on Windows and Linux) finds things across the console from any page. ## Related - [What is EvoHub](https://docs.evohub.io/what-is-evohub.md) - [EvoTrail overview](https://docs.evohub.io/evotrail-overview.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) --- Source: https://docs.evohub.io/get-help.md # Get help If something in EvoHub does not work as you expect, or you have a question or an idea, tell us from inside the console. Every request becomes a support ticket you can follow until it is answered. Opening tickets needs no permission: everyone in an organization can do it. ## Open a ticket :::steps ### Open Help & Support Open the avatar menu in the top bar and choose **Help & Support**. ### Start a new ticket Select **New Ticket**. ### Describe it Fill in **Subject** and **Message**, and choose a **Type** — **Support**, **Bug**, **Feature Request** or **Question** — and a **Priority**: **Low**, **Normal**, **High** or **Urgent**. ### Submit it Select **Submit Ticket**. It appears under **My Tickets**. ::: ## Report a bug quickly The bug button in the bottom-right corner of the console opens **Report a Bug** on any page. Describe under **What happened?** what you did, what you expected and what happened instead, and select **Send Report**. The report becomes a ticket titled "Bug report" that you can follow under **Help & Support**. ## Follow a ticket **Help & Support** opens on **My Tickets**: the tickets you opened, newest first, with counts of **Open**, **Resolved** and **Total** above the list. Each ticket shows its type, priority and status. | Status | Meaning | | --- | --- | | **Open** | Waiting for the support team. A new ticket starts here, and so does a ticket you reply to. | | **Pending** | The support team is waiting for something, usually an answer from you. | | **Resolved** | The support team considers it answered. Reply if it is not, and it opens again. | | **Closed** | Finished. A closed ticket takes no more replies; open a new ticket if you need further help. | Open a ticket to read the conversation. Messages from the team are marked **Support Team**, yours **You**. Write your answer under **Reply** and choose **Send Reply**. When the support team answers, you also get an email ("Re: — EvoHub Support") with a **View Reply** link to the ticket. Replying to that email does not add to the ticket; reply in the console. Your tickets are visible to you and to EvoHub's support team, not to other members of your organization. The **Open tickets** number and the **Waiting support tickets** widget on your [home page](https://docs.evohub.io/your-home-page.md) show the ones awaiting an answer. ### Limits - A subject can be up to 200 characters. Subject and message are required. - Support is for reaching the EvoHub team. It is not a help desk for your own customers. ## Other ways to reach us Write to **info@evosync.io**. To report a security problem, see [Security](https://docs.evohub.io/security.md#report-a-security-problem). ## Related - [Your home page](https://docs.evohub.io/your-home-page.md) - [What is EvoHub](https://docs.evohub.io/what-is-evohub.md) --- Source: https://docs.evohub.io/setup-tours-and-tips.md # Setup, the Guide and tours EvoHub takes you from "what do I want this to do" to it working: - A short **setup** when your account is new, where you also say what you want EvoHub to do first. - The **Guide** in the top bar of every screen. It shows the next step on your path and a button that does it, and it counts what you have done. - A **tour** of each area of the console, which starts the first time you open it, and a **tip** the first time you open one of its working screens. None of them is required. ## The setup flow When you open the console and your setup is not finished, Home sends you to **Setup** once per browser session. | Step | What you do | | --- | --- | | **About you** | Your title (type anything; suggestions appear as you type), and for your organization: **Sector**, **Engineering team size**, **What do you use today?** and **How did you hear about us?** All optional. If someone in your organization already answered the organization questions, they are not asked again. While your organization has no time zone and you may change its settings, **Your organization's time zone** is asked too, prefilled with your browser's; it is saved however you leave the step. See [Time zone](https://docs.evohub.io/organizations-and-teams.md#time-zone). | | **What do you want EvoHub to do first?** | Pick one or more products: On-Call, Status Pages, Uptime, Docs, Changelog, Retro, Board, Support. Pick what you need now; you can add the rest any time. Picking starts no charge. What you pick becomes your path in the Guide. | | **Phone number** | Pick the **Country**, enter your **Mobile number** and choose **Call me now**. EvoHub calls you at once: answer, listen, and press **1** to confirm the number. If no 1 is heard in time, choose **Call again**. | | **Notifications** | Turn on the ways you want to be paged: **Push on the EvoHub app** (sign in to the mobile app on your phone first; links to the App Store and Google Play are on the step), **Phone call** (needs the verified number) and **Email**. | | **Two-factor sign-in** | Scan the code with an authenticator app, type the 6-digit code it shows and choose **Turn on**. From your next sign-in, EvoHub asks for a code after your password. | | **Ready** | A summary of what is set up and what is not. **Start with …** opens the first product you picked and runs its tour; with nothing picked, **Go to Home**. **Invite your team** opens the invite dialog, if you may invite people. | Every step has **Continue**, **Skip this step** and **Skip setup for now**. Skipping costs nothing: the Guide brings any step back. A step that is already true of your account, such as a phone number you verified earlier, shows as done. If every step is already done when you first sign in, for example because you joined from an invitation and set everything up before, the setup is marked finished and never shown. ### Finish setup on Home Steps you skipped wait on Home in the **Finish setup** card, with how many of them are done. Each open step has its own button (for example **Add number** or **Set up**) that takes you back to it. **Hide** removes the card from Home; the Guide can show it again. ## The Guide **Guide** sits in the top bar of every screen, with how many steps of your path are done (for example **Guide 2/5**). It opens a panel with: - **Next step**: the first thing on your path that is not done yet, how long it takes, and **Do it now**, which opens the screen where it is done. If you have not said what you want to do, **Choose what to set up** opens that question. - **Take the … tour**: this screen's tour, when it has one. - **Your path**: for each product you picked, the steps that take it to its first real result, each with **Go**. **Change what you use** opens the question again. - **Your account**: the setup steps (about you, phone, notifications, two-factor), with **Set up** or **Change**, and **Show the setup card on Home** when you hid it. - **Every tour**, with **Start** or **Replay**. - **Help on screen**: whether tours start on their own, **Show "Get to know EvoHub" on Home** when you hid it, and **Show screen tips again**. A step on your path is done when your account shows it. Ticking a box does not count. For example, On-Call is done when an alert has actually arrived, and an integration with no policy is not enough. | Product | Steps | | --- | --- | | On-Call | Connect a tool that sends alerts · Choose who gets paged (an escalation policy) · Send the tool's alerts to that policy · Turn on how we reach you · Receive your first alert | | Uptime | Add a monitor · See its first check · Get told when it goes down (a notification channel) | | Status Pages | Create your status page · List what you run (components) · Put it on your domain | | Docs | Start your docs site · Write your first page | | Changelog | Start your changelog · Publish your first entry | | Retro | Run your first retrospective | | Board | Create your first board | | Everyone | Turn on two-factor sign-in, after the products' steps | The Guide reads your account again each time you open it, so a step you just finished shows as done. ## Product tours The first time you open a product or area, its tour starts on the screen you opened. A tour points at a few things on that screen, one step at a time, with **Back**, **Next** and **Done**. - **Skip tour**, or closing it, ends it. It counts as seen and does not start again. - **Don't show tours again** inside a tour stops every tour starting on its own. Turn it back on in the Guide or in **My Account → Preferences**. - The last step of most product tours offers a first action, such as **Connect your first integration**, **Add your first monitor** or **Create your first status page**. It opens the same short form the product uses. - A tour does not start on a form or detail screen that has a tip of its own; it waits for the product's main screen. ### The tour strip on Home Home shows **Get to know EvoHub**: a button for Home's own tour and one for each product tour you can take, with how many are done. Choosing one opens that product (or stays on Home) and runs its tour. While the strip shows, Home's own tour does not start on its own. **Hide** (the × on the strip) removes it, on every browser and computer you sign in on. The Guide can show it again. It is not shown while tours are turned off, nor once you have finished every tour. ### Every area has a tour - **Home**: the search (**⌘K**), the Guide, the **Finish setup** card, the number tiles, quick actions, your services and **Customize home**. - Each product you can open: On-Call, Uptime, Status Pages, Docs, Changelog, Retro, Board and EvoTrail. - **Support**, **Usage & Billing**, **Organization** and **My Account**. A tour covers the whole area, so its detail screens (an alert, a monitor, a statement) run the area's tour too. A product's tour starts only if your role can see something in that product. **Setup** has no tour: it is a guided walk-through already. Which tours you have taken is saved with your account, so a tour you have seen does not start again on another browser or computer. ## First-use tips A tip explains one working screen — a form, a detail page or an editor — the first time you open it, for example **New monitor**, **An alert**, **An escalation policy**, **New integration**, **The page editor** or **Inviting someone**. It points at the parts of that screen in a few short steps; **Got it** ends it. A step whose part is not on the screen is skipped. Tips follow the same switch as tours: with tours turned off, a tip you have not seen does not open on its own either. **No more tips or tours** inside a tip turns both off. ## Replay a tour or a tip, or turn them off - In the **Guide**: **Take the … tour** for this screen, **Start** or **Replay** for any tour, and the switch for tours that start on their own. - In **My Account → Preferences**: the same switch, every tour with **Start** or **Replay**, and each screen's tips. **Replay** on a tip makes it show once more the next time you open that screen. ## Related - [Create your account](https://docs.evohub.io/create-your-account.md) - [Your home page](https://docs.evohub.io/your-home-page.md) - [Notifications](https://docs.evohub.io/notifications.md) - [Two-factor authentication](https://docs.evohub.io/two-factor-authentication.md) --- Source: https://docs.evohub.io/on-call-overview.md # On-Call overview EvoHub On-Call receives alerts from your monitoring tools and makes sure the right person hears about them. This page explains the building blocks and how an alert travels from your monitoring tool to a person's phone. ## How an alert reaches someone ```mermaid flowchart LR A[Monitoring tool] -->|webhook or email| B[Integration] B --> C[Alert] C --> D[Escalation policy] D -->|step 1, 2, 3…| E[Person or schedule] E --> F[Voice call / push / email] ``` 1. **An integration receives the alert.** Each integration has its own webhook URL (or, for the email integration, its own inbound address). Your monitoring tool sends to it. 2. **EvoHub opens an alert.** The integration's parser reads the tool's payload and creates an alert with a title, severity, description and labels. If an open alert with the same fingerprint already exists, EvoHub counts a repeat on that alert instead of opening a new one. 3. **The integration's escalation policy starts.** The policy is a list of steps. Each step names a person or a schedule, how long to wait, and optionally how to notify. 4. **A schedule answers "who is on call now".** When a step targets a schedule, EvoHub looks at the schedule's layers, rotations and overrides to find the person on duty at that moment. 5. **The person is notified.** By default EvoHub uses the channels the person turned on for themselves (voice call, mobile push, email). A step can force one channel instead. 6. **Someone responds.** Acknowledging, taking over or resolving the alert stops the escalation. If nobody responds, the next step runs. 7. **The source recovers.** For most integrations, the recovery notification resolves the matching alert automatically. ## The building blocks | Concept | What it is | Where | | --- | --- | --- | | **Integration** | A connection to one monitoring tool, with its own URL and key, attached to an escalation policy. | **On-Call → Integrations** | | **Alert** | One problem reported by a tool (or created by hand). Has a status: triggered, acknowledged, resolved or suppressed. | **On-Call → Alerts** | | **Escalation policy** | Who is notified, in what order, after how long, and whether the chain repeats. | **On-Call → Escalation Policies** | | **Schedule** | Who is on call when. Made of layers; each layer is a rotation of people. | **On-Call → Schedules** | | **Override** | A temporary replacement on a schedule, for example to cover a vacation. | A schedule's **Overrides** section | | **Incident** | A human-declared, coordinated response with a timeline, which you can publish to a status page and write a postmortem for. | **On-Call → Incidents** | | **Maintenance** | A planned window during which On-Call pages nobody. | **On-Call → Maintenance** | On-Call does not have a separate "service" object. An alert is routed by the integration it arrived through, and that integration's escalation policy decides who is paged. ## Set up On-Call for the first time :::steps ### Turn on your own notifications Open **My Account → Notifications**. Turn on **Mobile Push** (sign in to the EvoHub mobile app first), **Phone Call** (verify your phone number in **Settings** first) and **Email**. See [Notifications](https://docs.evohub.io/notifications.md). ### Create a schedule Go to **On-Call → Schedules → New Schedule**, choose the timezone your team works in, and add a layer with the people who rotate. See [Schedules and rotations](https://docs.evohub.io/schedules-and-rotations.md). ### Create an escalation policy Go to **On-Call → Escalation Policies → New Policy**. Make step 1 target the schedule, and add a second step for a backup. See [Escalation policies](https://docs.evohub.io/escalation-policies.md). ### Connect a monitoring tool Go to **On-Call → Integrations → + Add Integration**, pick your tool, select the escalation policy and copy the webhook URL into the tool. See [Integrations overview](https://docs.evohub.io/integrations-overview.md). ### Send a test alert Trigger a test from your monitoring tool, or create one with **On-Call → Alerts → New Alert** and choose your escalation policy. Check that the right person is notified and can acknowledge it. ::: > [!TIP] > Moving from Opsgenie? The built-in importer creates schedules, escalation policies and integrations from your Opsgenie account. See [Migrate from Opsgenie](https://docs.evohub.io/migrate-from-opsgenie.md). ## Who can do what On-Call follows your organization roles. By default, Owners, Admins and Members can respond to alerts and configure schedules, escalation policies and integrations; Viewers can see everything but change nothing. Custom roles can grant individual On-Call permissions, such as responding to alerts without editing schedules. People who appear in rotations, escalation steps and overrides must hold the permission to respond to alerts. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). ## What counts toward usage On-Call is billed by usage, not per seat. Each new alert counts as one ingested alert; repeats of an alert that is still open do not. Voice calls and notification emails are metered as well; mobile push and outbound webhooks are free. See [How billing works](https://docs.evohub.io/how-billing-works.md) for current rates. ## Related - [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Mobile app](https://docs.evohub.io/mobile-app.md) --- Source: https://docs.evohub.io/schedules-and-rotations.md # Schedules and rotations A schedule answers one question: who is on call right now? Escalation policies point at schedules, so the person paged changes automatically as the rotation moves on. This page shows how to build schedules, layers and rotations. ## How a schedule is put together - A **schedule** has a name and a **timezone**. Every hour on the schedule — handover times, shift windows, the calendar — is read on that clock. - A schedule has one or more **layers**. Each layer is a **rotation**: an ordered list of people who take turns. - Each layer has a **priority**. Lower numbers come first. When several layers cover the same moment, the layer with the lowest priority number is the one that answers "who is on call", and the others are the depth behind it (see [Layers and escalation](#layers-and-escalation)). - **Overrides** replace whoever the layers say is on call for a period of time. See [Overrides and take over](https://docs.evohub.io/overrides-and-takeover.md). ## Create a schedule :::steps ### Open Schedules Go to **On-Call → Schedules** and click **New Schedule**. ### Name it and pick a timezone Enter a **Schedule Name** (for example "Platform On-Call") and choose the **Timezone** your team works in. ### Add a layer Open the schedule and click **Add Layer**. Fill in the fields described below and save. ### Add people to the rotation In the layer, use **Add member…** to add people in the order they take turns. Use the arrows to move someone earlier or later, and the remove button to take them out. ::: Only people who are allowed to respond to alerts can be added to a rotation. ## Change a layer without disturbing the current shift When you change who is in a layer or how it rotates, EvoHub asks **When should this take effect?**: | Choice | What happens | | --- | --- | | **When the current shift ends** | The default. Whoever is on call now finishes their turn; the change starts at the next handover. | | **When everyone has had their turn** | The current round of the rota finishes first. | | **From a date and time** | The change starts at the moment you pick, on the schedule's clock. | | **Now** | The change applies at once, and whoever is on call may change immediately. | **Start from the top of the list** makes the first person in the new list hold the rota when the change starts; otherwise whoever holds it at that moment keeps it. Before you save, **Next handovers** lists the coming turns with the change applied and says when it takes effect. Until then, the layer shows **Change scheduled** with the date and the new rota. **Cancel change** drops it, and the layer stays as it is. ## Layer settings | Field | What it does | | --- | --- | | **Layer Name** | A label, such as "Primary" or "Backup". | | **Rotation Type** | **Daily**, **Weekly**, or **Custom (every N days or hours)**. Weekly asks which day the rota changes hands (**Hands over on**). Custom asks how long each person holds the rota (**Rotate every** … **days**, 1 to 365, or **hours**, 1 to 168). | | **Starts on** | The day the rota begins. The first person in the list holds it first. This can only be set when you add a layer. | | **When it is on call** | **All day** (around the clock, handing over at a set time) or **A shift** (part of the day, with other rotas covering the rest). | | **Hands over at** | For an all-day layer: the time of day the rota passes to the next person. Left empty, the handover happens at whatever time the rotation started. | | **Priority** | Lower comes first. Within one shift, the lowest priority is paged first and the next is the backup. | ### Shifts Choose **A shift** to make a layer cover only part of the day: - Set the shift's start and end time, for example 08:00 to 16:00. An end time earlier than the start runs past midnight (22:00 to 06:00 is a night shift). The end time is exclusive, so 08:00–16:00 and 16:00–00:00 meet without overlapping. - Optionally pick **Only on these days**. With no days selected the shift runs every day. Days you leave out are off duty: nobody on this layer is paged then. This is which days the shift covers — not how often it changes hands. A team working three eight-hour shifts uses one schedule with three layers whose windows do not overlap. Keep them in one schedule rather than three, so a single escalation step can target "whoever is on call now". Once any layer on a schedule is a shift, the schedule shows a **Coverage over a day** bar. Hours that no layer covers are marked, with the message "Nobody is on call for part of the day". An alert arriving in an uncovered hour reaches no one on that schedule, and the alert's timeline says so ("No one was on call — this step reached nobody"). Leaving hours uncovered is allowed — just make sure it is intentional. ## Read the schedule The schedule page shows: - **Currently On-Call**: who holds the schedule right now and until when, on the schedule's clock. - A calendar of who is on call each day per layer, for **1W**, **2W** or **4W**, with arrows to move between weeks and **Today** to jump back. - **Layers**, with their rotation type, shift window and members. - **Overrides**, with the active one highlighted. **On-Call → Schedules** lists every schedule with **On-Call Now** for each. An active schedule that no escalation policy uses is flagged with **No escalation policy**, because nobody will be paged from it. ## Change the schedule timezone Click the timezone button next to the schedule name to open **Schedule timezone**. Changing it does not merely relabel the schedule: shift windows and handover times move by the difference between the two zones, and whoever is on call right now may change. Nothing is deleted. The dialog shows the same moment on both clocks before you save. ## Layers and escalation When an escalation step targets a schedule, it can target one specific layer or **Any layer (round-robin)**: - **A specific layer** pages whoever that layer has on call at the moment the step runs. - **Any layer (round-robin)** uses the step's position in the policy to pick among the layers that are on duty at that moment, in priority order: step 1 pages the first layer, step 2 the second, and so on, wrapping around. On a schedule with a primary and a backup layer, step 1 reaches the primary and step 2 the backup. Layers that are off duty (outside their shift) are skipped, so a step never pages a rota that is not working. Overrides always win: while an override is active, every step that targets the schedule reaches the override person. ## Delete a schedule or layer Use the trash icon on a layer to delete it, or the delete action in the schedules list to delete the whole schedule. Escalation steps that targeted a deleted schedule stop reaching anyone, so update your escalation policies first. ## Related - [Overrides and take over](https://docs.evohub.io/overrides-and-takeover.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) - [Migrate from Opsgenie](https://docs.evohub.io/migrate-from-opsgenie.md) --- Source: https://docs.evohub.io/overrides-and-takeover.md # Overrides and take over Plans change: someone goes on vacation, gets sick, or is already working on the problem that just paged a colleague. EvoHub handles this in two ways. An **override** changes who is on call on a schedule for a period of time. **Take over** makes a single alert yours. ## Overrides An override puts a replacement person on a schedule from a start time to an end time. While it is active, the override wins over every layer of the schedule: anyone who asks the schedule "who is on call" — the **Currently On-Call** banner, the mobile app and every escalation step that targets the schedule — gets the override person. ### Add an override in the console :::steps ### Open the schedule Go to **On-Call → Schedules** and open the schedule. ### Click Add Override In the **Overrides** section, click **Add Override**. ### Fill in the details Choose the **Replacement User**, the **Start Time** and **End Time**, and optionally a **Reason (optional)** such as "Vacation cover". The times are on the schedule's clock, whatever your own time zone; the picker shows its offset. ### Save The override appears in the list, marked **Active** while it is in effect, with the time range shown on the schedule's clock. ::: To remove an override, click the trash icon on its row. The schedule goes back to its normal rotation immediately. Only people who are allowed to respond to alerts can be chosen as a replacement. Adding and removing overrides needs permission to edit schedules (Owners, Admins and Members have it by default). ### Take or give a shift from the mobile app In the EvoHub mobile app, open the **Dashboard** tab, choose **Schedule** in its **On-Call** section, tap a day, and choose who covers it under **Assign coverage to** in the **Override shift** sheet. Choose yourself to take the shift, or a teammate to hand it over. The app creates an override for that whole day (midnight to midnight on your phone's clock). If the day is already covered by an override, the sheet shows it under **Currently overridden** with an option to remove it. ### Things to know - If two overrides overlap, the one that started first is used for the overlapping time. To change who covers a period, delete the old override rather than adding a second one on top. - An override covers the whole schedule, not one layer. On a schedule with a primary and a backup layer, the override person is reached by every step that targets that schedule. - Importing from Opsgenie or PagerDuty brings upcoming overrides over with their schedule. Their reason reads "Imported from Opsgenie" or "Imported from PagerDuty". ## Take over an alert **Take over** says "I've got this" for one alert. It: 1. Assigns the alert to you. 2. Acknowledges it, if it was still triggered. 3. Stops its escalation, so nobody else is paged for it. 4. Records **Taken over by** you (and from whom, if it was assigned to someone else) on the alert's timeline. You can take over an alert that is assigned to someone else or to nobody. You cannot take over a resolved alert. Where to find it: - **Console**: open the alert and click **Take over**. The button is hidden when the alert is already assigned to you. - **Bulk**: on **On-Call → Alerts**, select open alerts and click **Take over N**. - **Mobile app**: open the alert and tap **Take over**. - **Slack**: in the alert message's **…** menu, choose **Take over** (when Slack is connected; see [Slack](https://docs.evohub.io/slack.md)). Taking over needs permission to respond to alerts, which Owners, Admins and Members have by default. ### Take over or redirect? | You want to | Use | | --- | --- | | Own the alert yourself and stop paging others | **Take over** | | Hand the alert to a different team's escalation policy | **Redirect** — the current escalation stops and the selected policy is paged from the top | | Stop being paged for it while you investigate, without taking ownership | **Acknowledge** | See [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md) for all alert actions. ## Related - [Schedules and rotations](https://docs.evohub.io/schedules-and-rotations.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) - [Mobile app](https://docs.evohub.io/mobile-app.md) --- Source: https://docs.evohub.io/escalation-policies.md # Escalation policies An escalation policy is the chain EvoHub follows when an alert arrives: who to notify first, how long to wait for a response, and who to try next. Every integration points at one escalation policy. This page explains each setting and how the chain runs. ## How a policy runs 1. An alert arrives through an integration and the integration's policy starts. 2. **Step 1** runs after its delay (which can be **0 (now)**). It notifies its target. 3. If nobody acknowledges, takes over or resolves the alert, the next step runs after its own delay. Each step's delay counts from the step before it. 4. After the last step, the policy either stops or, if **Repeat escalation** is on, waits and starts again from step 1. The escalation stops as soon as someone: - **acknowledges** the alert (unless the policy has an acknowledgement timeout, see below), - **takes over** the alert, - **resolves** the alert, or the source resolves it, - **suppresses** it from a voice call. **Redirect** on an alert stops the current escalation and starts the selected policy from the top. See [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md). An alert that arrives through an integration with no escalation policy is recorded, but nobody is paged. ## Create a policy :::steps ### Open Escalation Policies Go to **On-Call → Escalation Policies** and click **New Policy**. ### Name it Enter a **Policy Name**, for example "Platform — default". ### Add steps Under **Escalation Steps**, set when each step notifies (**Notify after** … **min**) and choose its target: a **Schedule** (and optionally one of its layers) or a **User**. Add more steps with the add button. ### Choose whether to repeat Optionally tick **Repeat escalation** and choose how many times and how long to wait between cycles. ### Create Save the policy. Then open it and click **Edit** for the full set of options described below. ::: The **New Policy** dialog offers the common settings, with a shorter list of delays (step 1: **0 (now)**, 5, 15 or 30 minutes; later steps and the repeat wait: 5, 15 or 30). The policy's own page (**Edit**) adds per-step notification methods, retries, webhook targets, Slack and Teams channels and the acknowledgement timeout. ## Step settings | Setting | Options | What it does | | --- | --- | --- | | **Notify after** | First step: **0 (now)**, 1, 2, 3, 5, 10, 15, 30 or 60 minutes. Later steps: 1 to 60 from the same list. | How long to wait before this step runs, counted from the previous step (the first step from when the alert opened). | | **via** | **Default**, **Push**, **Call**, **Email** | **Default** uses each person's own enabled channels ("their preferences"). Choosing a method forces that one channel for this step. | | **Try** | **once** up to **10 times**, waiting 1 to 60 **min between** | Notifies the same target again before the policy moves on. "Call three times, two minutes apart, then wake the backup" is one step with **Try 3 times, waiting 2 min between**. | | Target | **Schedule**, **User**, **Webhook** | Who or what this step reaches. | | **Chat** → **Slack** | **Send to Slack** | Also posts the alert to a Slack channel when this step runs, in addition to paging the target. Available once Slack is connected. See [Slack](https://docs.evohub.io/slack.md). | | **Chat** → **Chat channel** | A Microsoft Teams channel from **Organization → Chat apps** | Also posts the alert to a Microsoft Teams channel when this step runs, in addition to paging the target. See [Microsoft Teams](https://docs.evohub.io/microsoft-teams.md). | ### Targets - **Schedule** pages whoever is on call on that schedule when the step runs. Pick a specific layer, or **Any layer (round-robin)** to let the step's position choose among the layers on duty. See [Layers and escalation](https://docs.evohub.io/schedules-and-rotations.md#layers-and-escalation). If nobody is on call at that moment, the step reaches nobody and the alert's timeline says "No one was on call — this step reached nobody". - **User** pages one person directly. Only people who can respond to alerts can be selected. - **Webhook** sends an HTTP `POST` to the URL you enter, so another system can react. The body is JSON: ```json { "alert_id": "6f1c2a4e-1b7d-4c55-9a0e-2f8b7d1c9e10", "title": "Database primary is down", "severity": "critical", "summary": "Replication lag over 30s, primary not answering on 5432", "step": 1 } ``` `summary` carries the alert's description. The request has a 10-second timeout and is sent with `Content-Type: application/json` and `User-Agent: EvoHub-OnCall/1.0`. ### Notification method and personal schedules When a step forces a method, only that channel is used for this step, and only if the person has it turned on in their own preferences; a person who has turned the forced channel off is not reached by this step, and the escalation carries on. A person's **Notification schedule** also applies: if their schedule does not allow the forced channel at that time, EvoHub uses the channels their schedule does allow instead, and if none are allowed, nothing is sent to them and the escalation carries on. See [Notifications](https://docs.evohub.io/notifications.md). SMS is not available as a method. A step saved with SMS in the past is shown as **SMS (no longer available)** and is reset to **Default** when you edit the policy. ## Policy settings ### Repeat escalation Turn on **Repeat escalation** to run the whole chain again when the last step has run and nobody responded. Choose **1** to **5** times and how many minutes to **wait** between cycles (1, 2, 3, 5, 10, 15, 30 or 60). ### If acknowledged but not resolved By default, acknowledging an alert ends its escalation. That can leave an alert open with nobody watching it if the person who acknowledged gets pulled away. To guard against that, set **If acknowledged but not resolved in** to a time (5 minutes to 4 hours). If the alert is still unresolved when the time runs out, the escalation resumes and the timeline records it. Choose **escalate again, up to** 1 to 5 times so it cannot ring all night, and choose **and call**: - **Someone else** — skips whoever acknowledged and looks for another pair of hands. - **Whoever acknowledged, then someone else** — reminds them once. If that goes unanswered, the next repeat escalates past them. **Never** (the default) turns this off. The setting is read each time, so turning it off also stops the reminders for alerts that are already acknowledged. ## Responding by phone During a voice call, the person called can press **4** to acknowledge, **3** to escalate to the next step right away, or **6** to suppress the alert. See [Notifications](https://docs.evohub.io/notifications.md#voice-calls). ## Delete a policy Open the policy and delete it; the **Delete Policy** dialog asks you to confirm. Escalations of the policy that are still running stop. > [!WARNING] > Before you delete a policy, open **On-Call → Integrations** and point every integration that uses it at another policy (or **— None —**). An integration left pointing at a deleted policy still opens alerts, but nobody is paged for them; each alert's timeline says the integration's escalation policy no longer exists. ## Related - [Schedules and rotations](https://docs.evohub.io/schedules-and-rotations.md) - [Notifications](https://docs.evohub.io/notifications.md) - [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md) - [Integrations overview](https://docs.evohub.io/integrations-overview.md) --- Source: https://docs.evohub.io/alerts-and-incidents.md # Alerts and incidents An **alert** is one problem reported by a monitoring tool (or raised by hand). An **incident** is a coordinated response your team declares, with a timeline, a public status page entry and a postmortem. This page covers both. ## Alert statuses | Status | Meaning | | --- | --- | | **Triggered** | New and unanswered. The escalation policy is running. | | **Acknowledged** | Someone is on it. The escalation stops (or pauses, if the policy has an acknowledgement timeout). | | **Resolved** | Over. Resolved by a person, or automatically by the source's recovery notification. | | **Suppressed** | Silenced from a voice call (key 6). Escalation stops. | "Open" means triggered or acknowledged. ## The Alerts page **On-Call → Alerts** lists your organization's alerts. - The status filter offers **All**, **Open** (the default), **Triggered**, **Acknowledged**, **Resolved** and **Suppressed**. **Open**, **Triggered** and **Acknowledged** show how many alerts they hold. - **Assigned to me** narrows the list to alerts assigned to you; it combines with the status filter. - **Search title or source…** searches as you type. - **Filters** adds **Severity**, **Source**, **Assignee**, **Escalation policy**, **Integration** and **Created between**. - Expand a row to see its description, custom details (labels) and fingerprint, or click **View full alert**. Each integration's detail dialog also has **View this integration's alerts**, which opens this list filtered to that integration. ## Respond to an alert Open an alert to see its details, labels and timeline. The actions available depend on its status and your permissions: | Action | What it does | | --- | --- | | **Acknowledge** | Marks the triggered alert as acknowledged and stops (or pauses) the escalation. | | **Take over** | Assigns the alert to you, acknowledges it if needed and stops the escalation. See [Overrides and take over](https://docs.evohub.io/overrides-and-takeover.md#take-over-an-alert). | | **Redirect** | Hands the alert to another escalation policy. Its current escalation stops and the selected policy is paged from the top. An acknowledged alert goes back to triggered so the new policy actually pages. | | **Resolve** | Closes the alert and stops the escalation. | | **Add a comment…** | Adds a note to the alert's timeline under your name. | Acknowledging an alert that is already acknowledged, or resolving one that is already resolved, does nothing and is not an error. A resolved alert cannot be acknowledged again. Responding (acknowledge, take over, resolve, notes) needs permission to respond to alerts. Redirecting and creating alerts by hand need permission to write alerts. Owners, Admins and Members have both by default. ### Bulk actions Tick alerts on the Alerts page (or **Select all open alerts**) to act on many at once: **Acknowledge N** (triggered alerts), **Take over N** (open alerts) and **Resolve N**. Each alert is processed on its own, so one that cannot be changed does not stop the rest. ## The alert timeline Every alert keeps a timeline of what happened to it, including: - **Alert created**, **Acknowledged**, **Resolved**, **Taken over by** …, **Redirected to another policy** - Notifications sent: **Voice call to** …, **Push notification to** …, **Email sent to** …, and the call result (for example **Not answered** or **Line busy**) - **Escalated to next step** when someone pressed 3 on a call - **No one was on call — this step reached nobody** - Notifications held back by a person's notification schedule, by a maintenance window, or because your organization has used its free allowance with no payment method on file - **Retriggered ×N** when the source sent the same alert again - Comments, including those added from Slack When someone asks "why wasn't I paged?", the alert's timeline is the place to look. ## Deduplication and auto-resolve Every alert has a **fingerprint** that identifies the problem. Integrations derive it from the source's own identifiers (for example, Alertmanager's fingerprint, a Zabbix host and trigger, or a PRTG sensor ID). - If an alert arrives while an alert with the same fingerprint is still open (triggered or acknowledged), EvoHub does not open a second one. It records **Retriggered** on the existing alert instead, and nobody is paged again. - Once the alert is resolved, the next alert with that fingerprint opens a new alert. - When the source reports recovery, EvoHub resolves the open alert with the matching fingerprint. Each integration page says how its source reports recovery. ## Create an alert by hand Click **New Alert** on the Alerts page, enter a **Title**, choose a **Severity** (**Critical**, **High**, **Medium**, **Low** or **Info**), optionally choose an escalation policy and add a **Description**. If you choose a policy, it starts paging immediately — this is a convenient way to test a policy end to end. ## Maintenance windows While a maintenance window is in progress, On-Call pages nobody in your organization. Alerts are still recorded, and each skipped notification shows on the alert's timeline as suppressed by maintenance. Schedule windows in **On-Call → Maintenance** (**New maintenance**); a window can also be shown on a status page and mute uptime monitors. See [Incidents and maintenance on status pages](https://docs.evohub.io/status-incidents-and-maintenance.md) and [Uptime alerts and silence windows](https://docs.evohub.io/uptime-alerts-and-silence-windows.md). ## Incidents An incident is a record your team declares for a significant problem. Incidents do not page anyone by themselves; they are where you coordinate, communicate and learn. ### Declare an incident Go to **On-Call → Incidents → New Incident** and enter: - **Title** and an optional **Summary** (you can edit both later). - **Severity**: **Critical**, **Major**, **Minor** or **None**. - **It already happened**: tick this to record an incident that has already ended, with its own **Started** and **Ended** times. Nobody is paged, and if you publish it to a status page it shows on the days it happened. Incidents are numbered (**INC-1**, **INC-2**, …). ### Work an incident An incident moves from **investigating** to **identified** (**Mark Identified**) to **resolved** (**Resolve**). On the incident page you can: - Edit the title, summary and severity. - Add timeline notes (**Add a timeline note…**), and edit or delete them later. - See the **Commander** and when it **Started**. **Resolve** asks **When was it resolved?**: - **Now**, when it has just been fixed. - **At another time**, for an incident fixed earlier and closed late. Pick the time it was really fixed; it cannot be before the incident started or in the future. That time is the one On-Call, the incident's timeline and, if the incident is published, the status page show as its end. ### Publish to a status page Click **Publish to status page**, choose the **Status page**, the **Public impact** (**None**, **Minor**, **Major**, **Critical**), the **Affected components**, and an optional **Public message**. Once published, the incident shows **On status page**, and EvoHub keeps the two in sync: marking it identified or resolved, and adding, editing or deleting timeline notes, update the public incident too. **Unpublish** removes it from the status page. Publishing an incident that has already ended puts it on the page's history on the days it happened; subscribers are not emailed. You need a status page first — see [Status Pages overview](https://docs.evohub.io/status-pages-overview.md). ### Postmortem After an incident is resolved, click **Postmortem** to write one. Start from a template (**Standard**, **5 Whys** or **Brief**) and fill in **Summary**, **Root cause**, **Contributing factors**, **Timeline**, **Action items** and **Lessons learned**. **Save internal** keeps it inside EvoHub; **Publish publicly** shows it on the incident's public status page entry (when the incident is published there). ## Related - [Escalation policies](https://docs.evohub.io/escalation-policies.md) - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Incidents and maintenance on status pages](https://docs.evohub.io/status-incidents-and-maintenance.md) - [Mobile app](https://docs.evohub.io/mobile-app.md) --- Source: https://docs.evohub.io/notifications.md # Notifications When an escalation step reaches you, EvoHub notifies you on the channels you have turned on. Your notification settings are personal: they belong to you, not to an organization, and apply in every organization you are a member of. This page explains the channels, your notification schedule and voice calls. ## Channels EvoHub notifies people by: | Channel | Shown as | Requirement | | --- | --- | --- | | Voice call | **Phone Call** | A verified phone number. | | Mobile push | **Mobile Push** | You are signed in to the EvoHub mobile app on at least one iOS or Android device, with notifications allowed. | | Email | **Email** | Your account email. Turned on automatically. | > [!NOTE] > EvoHub does not send SMS yet; SMS is coming soon. Use push or voice calls for urgent paging. ## Turn your channels on :::steps ### Open Notifications Click your avatar and open **My Account → Notifications**. ### Verify your phone number If you see **Phone number not verified**, click **Verify it in Settings →** and verify your number. Once it is verified, **Phone Call** can be turned on, and it shows the number EvoHub will call. ### Install the mobile app Install EvoHub from the [App Store](https://apps.apple.com/us/app/evohub/id6799893875) or [Google Play](https://play.google.com/store/apps/details?id=io.evohub), sign in, and allow notifications. **Mobile Push** then shows **Sends to your mobile devices**. ### Turn the toggles on Switch on each channel you want to be reached on. ::: With an escalation step set to **Default**, you are notified on every channel you turned on. A step can also force one channel (**Push**, **Call** or **Email**) for everyone it reaches; it is used only for people who have that channel turned on. See [Escalation policies](https://docs.evohub.io/escalation-policies.md#step-settings). ## Notification schedule The **Notification schedule** limits which channels may reach you at which times — for example, no phone calls during working hours, or only push at night unless the alert is critical. - Pick the **Timezone** your rules are written in. - Click **Add rule** for each time window (up to 20). Each rule has: - the days it applies to, - **From** and **to** times, - **Allowed:** the channels that may reach you in that window (choosing none means nothing reaches you then), - **Critical alerts still use all my channels**, to let critical alerts through regardless. - Click **Save schedule**. How rules are read: - **The first rule that matches wins.** Use the arrows to put more specific rules first. - Outside every rule, you are reached on every channel you turned on. - A rule's days are the days its window *starts*. A Monday 23:00–08:00 rule covers Monday night and early Tuesday morning, not early Monday morning. - An end time before the start runs past midnight. The same start and end time (for example 00:00–00:00) means the whole day. The end time is exclusive. - A rule only narrows the channels you turned on above; it never turns on a channel you switched off. When a step forces a channel your schedule does not allow at that moment, EvoHub uses the channels your schedule does allow instead. If none are allowed, nothing is sent to you and the escalation continues to its next step as it would for anyone who did not respond. Either way, the alert's timeline records that your notification schedule held a notification back. ## Voice calls A voice call announces the alert and lets you act on it with the keypad: | Key | Action | | --- | --- | | **4** | Acknowledge the alert. The escalation stops (or pauses, if the policy has an acknowledgement timeout). | | **3** | Escalate to the next step now. | | **6** | Suppress the alert. The escalation stops. | If you do not press a key, the escalation continues as usual. The result of each call (for example connected, not answered or line busy) is recorded on the alert's timeline. When several alerts reach you within a short time, EvoHub combines them into one call that names how many alerts are waiting; pressing 4 acknowledges all of them. Push notifications are still sent for each alert as it arrives. ### Customize what the call says Organizations can replace the sentence that names the alert with their own wording in **On-Call → Voice Template**. The greeting and the key instructions always stay. - Write the **Template** with fields in braces, for example `{customer} customer, {service} service.` Insert fields from **About the alert** (available on every alert, such as `{alert.title}` and `{alert.severity}`) or **Sent by your integrations** (labels your alerts have carried). - A sentence that names a field the alert does not carry is left out of the call, so a call never reads out an empty gap. - The template can be up to 1,000 characters; what is read out on a call is cut at 200 characters. Commas and full stops become pauses. - **What the call says** previews the spoken text and warns about fields no alert has sent. - An empty template restores the default call. When several alerts are combined into one call, the template is not used. Editing the voice template needs the On-Call settings permission (Owners, Admins and Members by default). ## Push notifications Push notifications go to every device where you are signed in to the EvoHub mobile app. Tap one to open the alert. On iPhone, a push also offers an **Acknowledge** button so you can acknowledge without opening the app. See [Mobile app](https://docs.evohub.io/mobile-app.md). ## Email Alert emails go to your account email address. ## When notifications are not sent Check the alert's timeline. Common reasons: - **No one was on call** on the schedule the step targeted. - A **maintenance window** was in progress, so On-Call paged nobody. - Your **notification schedule** held the channel back. - The channel is not set up: phone number not verified, or no device signed in to the mobile app. - Your organization has used its free monthly allowance and has no payment method on file. Alerts are still recorded, but notifications are held back and the timeline says why. See [How billing works](https://docs.evohub.io/how-billing-works.md). ## Test your setup Create an alert by hand with **On-Call → Alerts → New Alert** and choose an escalation policy whose first step targets you (or a schedule you are on). Check that your phone rings, your push arrives and you can acknowledge. ## Related - [Escalation policies](https://docs.evohub.io/escalation-policies.md) - [Mobile app](https://docs.evohub.io/mobile-app.md) - [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md) --- Source: https://docs.evohub.io/mobile-app.md # Mobile app The EvoHub mobile app is your on-call companion. It is built for responding when you are away from your desk: getting paged, acknowledging, taking over and resolving alerts, and covering a shift. Configuration — schedules, escalation policies, integrations — stays in the web console. ## Get the app - iPhone: [EvoHub on the App Store](https://apps.apple.com/us/app/evohub/id6799893875) - Android: [EvoHub on Google Play](https://play.google.com/store/apps/details?id=io.evohub) Sign in with your email and password, or with **Continue with Google** or **Continue with GitHub**. If your organization requires two-factor authentication, set it up in the web console first; the app then asks for your code when you sign in. After you sign in, allow notifications. Your device is registered for push, and **Mobile Push** turns on in your notification settings. See [Notifications](https://docs.evohub.io/notifications.md). ## What you can do ### Alerts The **On-Call** tab lists your organization's alerts, filtered by **Open**, **Triggered**, **Ack**, **Resolved** or **All**. Open an alert to see its details, labels and timeline, and to act on it: - **Acknowledge** — you are on it; escalation stops. - **Take over** — assign it to yourself, acknowledge it and stop escalation. - **Redirect** — hand it to another escalation policy, which is paged from the top. - **Resolve** — close it. - **Add a note…** — write on the alert's timeline. The buttons you see depend on the alert's status and your permissions. ### Push notifications Each alert that reaches you arrives as a push notification. Tap it to open the alert. On iPhone, the notification also has an **Acknowledge** button that acknowledges the alert without opening the app; if that fails, you get a follow-up notification asking you to open the alert. Manage the devices that receive push under **Profile → Push Notification Devices**. Removing a device stops push to it until you register it again. ### Schedules and shifts **Schedule**, **Incidents** and **Silence** (and **Escalation Policies** and **Maintenance**) open from the **On-Call** section of the **Dashboard** tab. **Schedule** shows who is on call each day, per layer, including shift windows. If you can edit schedules, tap a day to open **Override shift** and assign coverage to yourself or a teammate for that whole day, or remove an existing override. See [Overrides and take over](https://docs.evohub.io/overrides-and-takeover.md). ### Incidents **Incidents** lists incidents. Open one to read its updates, add an update, acknowledge it and resolve it. ### Silence **Silence** pauses paging and uptime alerts for the whole organization for **30 min**, **1 hour**, **4 hours** or **8 hours** — for example while you deploy a fix you know will trip monitors. The app asks you to confirm, because no one is paged during that time. You can end the silence early from the same screen. Silencing needs permission to manage maintenance windows and uptime silences. ### Also in the app - **Escalation Policies** and **Maintenance** in the Dashboard's **On-Call** section, to look up who gets paged and what maintenance is scheduled or active. - The **Dashboard**, **Uptime** and **Status** tabs, for an at-a-glance view across On-Call, uptime monitors and status pages. - **Profile**: **Notification Preferences** (turn **Mobile Push**, **Voice Call** and **Email** on or off), **Biometric Unlock** (Face ID, Touch ID or fingerprint), switching between organizations, **Support** and **Delete Account**. ## What the app does not do Some things are only available in the web console at [https://evohub.io](https://evohub.io): - Creating alerts or incidents. - Creating or editing schedules, layers and rotations (the app can add and remove day overrides). - Creating or editing escalation policies and integrations. - Notification schedules and the voice template. ## Related - [Notifications](https://docs.evohub.io/notifications.md) - [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md) - [Overrides and take over](https://docs.evohub.io/overrides-and-takeover.md) --- Source: https://docs.evohub.io/migrate-from-opsgenie.md # Migrate from Opsgenie Atlassian ended new Opsgenie sales on June 4, 2025, and Opsgenie reaches end of support on April 5, 2027. EvoHub On-Call has a built-in importer that reads your Opsgenie account and creates the matching schedules, escalation policies and integrations. This page explains how to run it, what it maps and what you finish by hand. ## How Opsgenie concepts map to EvoHub | Opsgenie | EvoHub | Notes | | --- | --- | --- | | Users | Organization members | Matched by email. Invite everyone — EvoHub has no per-seat fee. | | Teams | Teams | Not created. You choose which of your EvoHub teams each Opsgenie team's schedules, policies and integrations go into. | | Schedules and rotations | Schedules and layers | Imported. | | Schedule overrides | Overrides | Upcoming ones are imported with their schedule. | | Escalations | Escalation policies | Imported. | | Integrations (Prometheus, Datadog, CloudWatch, email, API…) | Integrations | Imported, each with a new URL to paste into the tool. | | Heartbeats | Uptime heartbeat monitors | Not imported. See [Heartbeat monitors](https://docs.evohub.io/heartbeat-monitors.md). | | Notification rules | Notification settings and notification schedule | Not imported; each person sets their own. | | SMS notifications | Push or voice call | EvoHub does not currently send SMS. | | Mobile app | EvoHub iOS and Android apps | See [Mobile app](https://docs.evohub.io/mobile-app.md). | ## Before you import - **Invite your people first.** The importer matches Opsgenie users to EvoHub members by email address. A schedule or escalation policy that names someone who is not a member yet is not imported until they join; the preview says which person is missing. The preview lists unmatched people and, if you are allowed to invite, offers an **Invite N people** button. - **Make sure they can respond to alerts.** A member whose role cannot respond to alerts holds back the schedules and policies they appear in the same way, with a message naming them. - **Create an Opsgenie API key with read access.** In Opsgenie: **Settings → API key management → Add new API key**, give it read access and copy it. You need the On-Call settings permission to import (Owners, Admins and Members have it by default). ## Run the importer :::steps ### Open the importer Go to **On-Call → Import from Opsgenie** (also linked from the **Moving from Opsgenie or PagerDuty?** banner on the Integrations page). ### Enter the key and region Paste the **Opsgenie API key** and choose the **Region**: **US** (api.opsgenie.com) or **EU** (api.eu.opsgenie.com). Click **Preview**. The key is used for this import only; EvoHub does not store it. ### Review the preview Nothing is created yet. The preview shows **People** (who matched), **Teams** (where each Opsgenie team's objects go — by default, your current team), **Schedules** with their layers, **Escalation policies** with their steps and delays, **Integrations** with the EvoHub type each maps to, and every warning. Untick anything you do not want. ### Import Click **Import N items**. The result lists what was created (with links), what was **Skipped** and why, and the warnings. ### Point your tools at EvoHub For each imported integration, the result shows its new webhook URL with "Paste this into … in place of the Opsgenie URL". Update each monitoring tool. Alerts reach EvoHub only after you do this. ::: You can run the importer again at any time. Items that were already imported are marked and skipped, so a re-run never creates duplicates. Only one import runs at a time per organization. A schedule imported before that people have joined since shows **Add N new people**. Tick it and import: they are put where Opsgenie has them in the rotation. If you changed the layer in EvoHub since the import, they are added at the end instead and a warning asks you to check the order. Nobody is removed. ## What is imported, and how ### Schedules Each Opsgenie schedule becomes an EvoHub schedule in the same timezone, and each rotation becomes a layer: - **Daily** and **weekly** rotations carry over. A rotation of several days or weeks becomes a custom rotation of that many days. - **Hourly** rotations become custom rotations of that many hours (up to 168). Longer hourly rotations are rounded to whole days, with a warning. - **Time restrictions** become shift windows when EvoHub can express them: a daily window, or the same window on selected weekdays (such as Mon–Fri 09:00–17:00). A schedule with a restriction EvoHub cannot express (such as Friday 18:00 to Monday 09:00) is not imported; the preview tells you to create that layer by hand, one layer per window. - Rotations that have already ended are skipped. Rotations that start or end in the future are imported with that start or end date. ### Overrides Every override that has not ended yet comes over with its schedule, for the same person and the same start and end. The preview shows how many each schedule brings. An EvoHub override covers the whole schedule for one person, so these are not imported, and the preview names each one for you to recreate: - an override on only some of a schedule's rotations, when the schedule has more than one; - an override handed to a team rather than a person; - an override for someone who is not in EvoHub yet, or cannot respond to alerts. Invite them or change their role, then run the import again. If Opsgenie does not return a schedule's overrides, the schedule is still imported and the preview says so. Running the import again adds overrides that are new since; one that is already on the schedule is not added twice. - Rotation participants are kept in order. A rotation with a team, an escalation, an empty ("no one") slot or the same person twice cannot be imported as it is: its schedule is not imported, and the preview tells you what to change in Opsgenie or to create the layer by hand. - A disabled Opsgenie schedule is imported as inactive. ### Escalation policies Each Opsgenie escalation becomes an escalation policy, and each rule a step: - Steps can target a **user** or a **schedule**. A rule that notifies a team becomes a step for that team's schedule when the team owns exactly one; otherwise it is skipped with a warning, as are rules that notify another escalation. A rule that notifies a schedule whose rotations overlap becomes one step per rotation, a minute apart. - Opsgenie counts every rule's delay from when the alert was created; EvoHub counts each step's delay from the step before, using 1, 2, 3, 5, 10, 15, 30 or 60 minutes. The importer converts the delays so each step lands as close as possible to its Opsgenie time and warns about any difference. - A rule that notifies the "next" or "previous" on-call of a schedule notifies whoever is on call now in EvoHub. - Opsgenie rules that escalate "if not closed" become steps that escalate until someone acknowledges. - Repeat settings carry over, up to EvoHub's maximum of 5 repeats. - Steps use each person's own notification preferences. ### Integrations - Integrations that send alerts *into* Opsgenie are imported as the matching EvoHub integration type (Prometheus/Alertmanager, Grafana, Datadog, New Relic, CloudWatch, Azure Monitor, Dynatrace, Sentry, Google Cloud, Zabbix, UptimeRobot, Pingdom, Site24x7, StatusCake, Nagios, PRTG, email and API). - A type EvoHub has no equivalent for is imported as a generic **API** integration, with a warning to check the payload it sends. - Integrations that only send *out* of Opsgenie — Slack, Microsoft Teams, Jira, ServiceNow, Zendesk, outgoing webhooks, Statuspage — are not imported. - Disabled Opsgenie integrations are not imported. - Each integration is attached to the first (by name) escalation policy of its Opsgenie team that is imported. If none is, it is created without a policy — choose one on the Integrations page. ## What you recreate by hand The preview lists these under **Not imported yet**: - Heartbeats — use [heartbeat monitors](https://docs.evohub.io/heartbeat-monitors.md). - Alert policies and routing rules. - Notification rules — each person sets [their own notifications](https://docs.evohub.io/notifications.md). - Maintenance windows — schedule them in **On-Call → Maintenance**. - Team members — Opsgenie teams are not created. Each team's objects go into the EvoHub team you choose in the preview, or your current team. Also recreate: - **Overrides** the preview flagged (see [Overrides](#overrides)). - **People who joined after the import, on escalation steps.** A re-run adds them to the schedules it imported (see above), but not to escalation policies imported before; add them to those steps by hand. ## Run both in parallel, then cut over :::steps ### Add EvoHub next to Opsgenie Where a tool supports several destinations (Alertmanager receivers, Datadog webhooks, CloudWatch SNS subscriptions, email recipients), keep Opsgenie and add the EvoHub URL, so both receive the same alerts for a week or two. ### Test paging end to end Fire a test alert through each integration. Confirm the right person is paged, can acknowledge from the mobile app, and that recovery resolves the alert where the source supports it. ### Compare During the parallel run, compare what EvoHub paged with what Opsgenie paged. ### Cut over Remove the Opsgenie destinations from your tools and replace heartbeats and API calls. Leave time for a quiet week with EvoHub as the only pager before Opsgenie's end of support. ::: The import itself is free. Alerts and notifications in EvoHub are billed by usage — see [How billing works](https://docs.evohub.io/how-billing-works.md). For help planning a migration, contact info@evosync.io. ## Related - [Migrate from PagerDuty](https://docs.evohub.io/migrate-from-pagerduty.md) - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Schedules and rotations](https://docs.evohub.io/schedules-and-rotations.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/migrate-from-pagerduty.md # Migrate from PagerDuty EvoHub On-Call has a built-in importer that reads your PagerDuty account with a read-only REST API key and creates the matching schedules, escalation policies and integrations. This page explains how to run it, what it maps and what you finish by hand. ## How PagerDuty concepts map to EvoHub | PagerDuty | EvoHub | Notes | | --- | --- | --- | | Users | Organization members | Matched by email. Invite everyone — EvoHub has no per-seat fee. | | Teams | Teams | Not created. You choose which of your EvoHub teams each PagerDuty team's schedules, policies and integrations go into. | | Schedules and layers | Schedules and layers | Imported, in the same time zone. | | Layer restrictions | Shift windows | Imported when EvoHub can express them (see below). | | Schedule overrides | Overrides | Those in the next 90 days are imported with their schedule. | | Escalation policies | Escalation policies | Imported, including loops. | | Services | — | EvoHub has no services. Each service's integrations are imported with the service's name. | | Service integrations (Events API v2, email, CloudWatch, Datadog…) | Integrations | Imported, each with a new URL to point the sender at. | | Event orchestrations and rules | — | Not imported. | | Notification rules | Notification settings and notification schedule | Not imported; each person sets their own. | ## Before you import - **Invite your people first.** The importer matches PagerDuty users to EvoHub members by email address. A schedule or escalation policy that names someone who is not a member yet is not imported until they join; the preview says which person is missing. The preview lists unmatched people and, if you are allowed to invite, offers an **Invite N people** button. People who join later can still be added: see [Run it again](#run-it-again). - **Make sure they can respond to alerts.** A member whose role cannot respond to alerts holds back the schedules and policies they appear in the same way, with a message naming them. - **Create a read-only REST API key.** In PagerDuty: **Integrations → API Access Keys → Create New API Key**, tick **Read-only API Key** and copy it. A user's personal REST API key also works but only sees what that user can see. You need the On-Call settings permission to import (Owners, Admins and Members have it by default). ## Run the importer :::steps ### Open the importer Go to **On-Call → Import from PagerDuty** (also linked from the **Moving from Opsgenie or PagerDuty?** banner on the Integrations page). ### Enter the key and service region Paste the **PagerDuty REST API key** and choose the **Service region**: **US** (api.pagerduty.com) or **EU** (api.eu.pagerduty.com). Click **Preview**. The key is used for this import only; EvoHub does not store it. ### Review the preview Nothing is created yet. The preview shows **People** (who matched), **Teams** (where each PagerDuty team's objects go — by default, your current team), **Schedules** with their layers, **Escalation policies** with their steps and delays, **Integrations** with the EvoHub type each maps to, and every warning. Untick anything you do not want. ### Import Click **Import N items**. The result lists what was created (with links), what was **Skipped** and why, and the warnings. ### Point your senders at EvoHub For each imported integration, the result shows its new URL. Update each sender. Alerts reach EvoHub only after you do this. ::: Only one import runs at a time per organization. If PagerDuty limits the key's requests, the importer waits and retries; if it is still limited, wait a minute and try again. ## What is imported, and how ### Schedules Each PagerDuty schedule becomes an EvoHub schedule in the same time zone, and each layer becomes a layer. PagerDuty's top layer — the one that wins — becomes EvoHub's first layer. - **Turn lengths** of whole days carry over: one day is daily, seven days is weekly, any other number of days is a custom rotation. Turns of whole hours, up to 168, become custom rotations of that many hours; other turn lengths are rounded to whole days, with a warning. - **Restrictions** become shift windows when EvoHub can express them: one daily window (it may run past midnight, such as 22:00–06:00), the same window on selected weekdays (such as Mon–Fri 09:00–17:00), or whole days (such as Saturday and Sunday). Two daily windows, or a weekly restriction that runs across days (Friday 18:00 to Monday 09:00), cannot be expressed: that schedule is not imported, and the preview tells you to create the layer by hand, one layer per window. - Layers that have already ended are skipped. A layer that starts or ends in the future is imported with that start or end date. - People are kept in order. A layer with the same person on more than one turn, or with a user the key cannot read, cannot be imported as it is: its schedule is not imported, and the preview says why. ### Overrides Overrides that have not ended and start within the next 90 days come over with their schedule, for the same person and the same start and end. The preview shows how many each schedule brings. An override for someone who is not in EvoHub yet, or cannot respond to alerts, is not imported and the preview names it: invite them or change their role, then run the import again. Overrides further ahead than 90 days come over on a later run. If PagerDuty does not return a schedule's overrides, the schedule is still imported and the preview says so. An override already on the schedule is never added twice. ### Escalation policies Each PagerDuty escalation policy becomes an escalation policy, and each target of a rule becomes a step: - Steps can target a **user** or a **schedule**. - PagerDuty's delay is the wait *after* a rule; EvoHub's is the wait *before* a step, using 1, 2, 3, 5, 10, 15, 30 or 60 minutes. The importer converts them so each step lands as close as possible to its PagerDuty time and warns about any difference. - A rule with several targets notifies them at once in PagerDuty; in EvoHub each target is its own step, a minute apart. - **Loops** become repeats, up to EvoHub's maximum of 5, after the last rule's delay. ### Integrations PagerDuty services are not imported as services — EvoHub has none. Instead, each integration of an active service becomes an EvoHub integration named **Service · Integration** (for example "Checkout API · Events API v2"), attached to the service's escalation policy if that policy is imported. - **Events API v2** integrations become EvoHub **API** integrations, which accept the Events API v2 format as it is: the same body, the same `dedup_key`, and the same answer (`202` with `status`, `message` and `dedup_key`). The sender changes only two things: the URL (`https://events.pagerduty.com/v2/enqueue` → the integration's URL) and the `routing_key`, which must be the new integration's key. A `routing_key` in the body is read before the `?key=` in the URL, so the old one must not stay. - An integration made for a tool EvoHub has its own integration for (Datadog, Prometheus, Grafana, New Relic, Amazon CloudWatch, Sentry and others) becomes that integration; point the tool at its URL in the tool's own settings. - **Email** integrations become email integrations with a new address. - **Events API v1** integrations are imported, with a warning: EvoHub reads the v2 format, so switch the sender to v2 when you point it at EvoHub. **Change events** are not read. - Integrations of disabled services are not imported. ## Run it again You can run the importer again at any time. Items that were already imported are marked and skipped, so a re-run never creates duplicates. A schedule imported before that people have joined since — they were invited after the first run, or were given a role that can respond — shows **Add N new people**. Tick it and import: they are put where PagerDuty has them in the layer. If you changed the layer in EvoHub since the import, they are added at the end instead and a warning asks you to check the order. Nobody is removed. Adding people changes who is on call from then on, so check the schedule afterwards. ## What you recreate by hand The preview lists these under **Not imported yet**: services, team members, event orchestrations and rules, response plays, business services and status pages, maintenance windows, notification rules and change events. Recreate upcoming **maintenance windows** in EvoHub, any **overrides** the preview flagged, and ask each person to set [their own notifications](https://docs.evohub.io/notifications.md). ## Run both in parallel, then cut over Where a sender supports several destinations, add the EvoHub URL next to PagerDuty for a week or two, fire a test alert through each integration, and compare what each paged. Then remove the PagerDuty destinations. The import itself is free. Alerts and notifications in EvoHub are billed by usage — see [How billing works](https://docs.evohub.io/how-billing-works.md). For help planning a migration, contact info@evosync.io. ## Related - [Migrate from Opsgenie](https://docs.evohub.io/migrate-from-opsgenie.md) - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Schedules and rotations](https://docs.evohub.io/schedules-and-rotations.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/integrations-overview.md # Integrations overview An integration connects one monitoring tool to EvoHub On-Call. It gives the tool a unique URL (or, for email, a unique address) to send alerts to, and it decides which escalation policy pages people for those alerts. This page lists every supported integration and explains how ingest works. ## Add an integration :::steps ### Open Integrations Go to **On-Call → Integrations** and open the **+ Add Integration** tab. ### Pick your tool Search or browse the catalog and click the tool. ### Name it and choose a policy Enter an **Integration Name** and choose the **Escalation Policy** that should page people for its alerts. Click **Create Integration**. ### Copy the URL into your tool The new integration shows its **Webhook URL** (or **Inbound email address**). Open the integration and click **Show config example** for setup steps with your URL already filled in. ::: Creating, editing and deleting integrations needs permission to write integrations (Owners, Admins and Members by default). Reading the list needs permission to read integrations, because each integration carries its key. ## The webhook URL and key Each integration's URL looks like this: ```text https://evohub.io/ingest/?key= ``` - `` selects the parser for the tool, for example `prometheus` or `datadog`. - The **integration key** (shown as **API Key** in the integration) identifies the integration. It decides the organization and the escalation policy; nothing in the request body can override it. - No other authentication is needed: monitoring tools cannot sign in, so the key in the URL is the credential. Treat the URL like a password. If it leaks, delete the integration and create a new one. The **API** integration also accepts the key in the request body or an `Authorization: Bearer` header. The **Email** integration uses an inbound email address instead of a URL. ## Signing secrets GitHub, GitLab, Jenkins and SonarQube can prove that a delivery came from them. Their integrations have a **Signing secret** section: - **Generate secret** creates a 64-character secret and shows it **once** — copy it into the tool. **Rotate** replaces it; the old one stops working at once. - **Use my own** stores a secret already set in the tool: 16 to 256 characters, no spaces, at least 8 of them different. - **Remove** goes back to the key alone. With a secret set, EvoHub checks every delivery's signature before reading it, and refuses a missing or wrong one with `403 INVALID_SIGNATURE`. The integration's key is then refused on any URL that checks no signature (`403 SIGNATURE_REQUIRED`), so the key alone is no longer enough. Without a secret, the key in the URL is accepted as for every other integration — the integration says **Not set** and suggests setting one. The secret is stored encrypted and is never shown again, not even to admins; the integration only says **Configured**. GitHub and SonarQube sign the body but not the time, so a captured delivery would verify again if resent; that can only retrigger or resolve the same alert. ## Manage an integration Click an integration on the **Installed** tab to open it. You can: - copy the **Webhook URL** and **API Key**, - see **Last Event**, when it last received something, - **View this integration's alerts**, - **Edit** its name and escalation policy, - set, rotate or remove the **Signing secret** (GitHub, GitLab, Jenkins and SonarQube), - **Disable** it (requests are refused until you **Enable** it again), or - **Delete** it — its URL and key stop working immediately. An integration with no escalation policy still records alerts, but nobody is paged. ## Supported integrations The catalog has 44 integrations. AWS CloudTrail has no integration of its own: it pages through the AWS CloudWatch one, so it is listed separately below. | Integration | Type in URL | How it connects | Auto-resolve | Setup | | --- | --- | --- | --- | --- | | **API** | `api` | Your own scripts and tools send JSON; trigger, acknowledge or resolve. | Send `event_action: resolve` with the same `dedup_key`. | [Alert API and generic webhook](https://docs.evohub.io/generic-webhook.md) | | **Webhook** | `alerts` | Any tool that can POST flat JSON; also accepts flat `alert_*` fields. | Send `status` (or `alert_status`) `resolved` with the same fingerprint or title. | [Generic alert webhook](https://docs.evohub.io/generic-webhook.md#generic-alert-webhook) | | **Prometheus** | `prometheus` | Alertmanager webhook receiver. | Yes, with `send_resolved: true`. | [Prometheus](https://docs.evohub.io/prometheus-alertmanager.md) | | **Grafana** | `grafana` | Grafana Alerting webhook contact point. | Yes. | [Grafana](https://docs.evohub.io/grafana.md) | | **Datadog** | `datadog` | Datadog Webhooks integration, mentioned in monitor messages. | Yes, when the monitor recovers. | [Datadog](https://docs.evohub.io/datadog.md) | | **Zabbix** | `zabbix` | Webhook media type with a short script. | Yes, with a recovery message. | [Zabbix](https://docs.evohub.io/zabbix.md) | | **PRTG** | `prtg` | "Execute HTTP Action" notification template. | Yes, when the sensor is Up again. | [PRTG](https://docs.evohub.io/prtg.md) | | **Email** | — | A unique inbound email address. | Yes, from a follow-up email that says the issue recovered. | [Email](https://docs.evohub.io/email-integration.md) | | **New Relic** | `newrelic` | Webhook destination used by an alert workflow. | Yes, when the issue is closed. | [New Relic](https://docs.evohub.io/new-relic.md) | | **AWS CloudWatch** | `cloudwatch` | SNS topic with an HTTPS subscription. | Yes, with the OK action set. | [AWS CloudWatch](https://docs.evohub.io/aws-cloudwatch.md) | | **Azure Monitor** | `azuremonitor` | Action group webhook with the common alert schema. | Yes. | [Azure Monitor](https://docs.evohub.io/azure-monitor.md) | | **Dynatrace** | `dynatrace` | Problem notification, custom integration. | Yes. | [Dynatrace](https://docs.evohub.io/dynatrace.md) | | **Sentry** | `sentry` | Internal integration with alert rule action. | Yes, when the issue is resolved or the metric alert resolves. | [Sentry](https://docs.evohub.io/sentry.md) | | **Google Cloud** | `googlecloud` | Cloud Monitoring webhook notification channel. | Yes, when the incident closes. | [Google Cloud Monitoring](https://docs.evohub.io/google-cloud-monitoring.md) | | **UptimeRobot** | `uptimerobot` | Webhook integration (or an older Web-Hook alert contact). | Yes, when the monitor is up. | [UptimeRobot](https://docs.evohub.io/uptimerobot.md) | | **Pingdom** | `pingdom` | Webhook integration assigned to checks. | Yes, when the check recovers. | [Pingdom](https://docs.evohub.io/pingdom.md) | | **Site24x7** | `site24x7` | Third-party webhook integration. | Yes, when the monitor is up. | [Site24x7](https://docs.evohub.io/site24x7.md) | | **Statuscake** | `statuscake` | Contact group webhook URL. | Yes, when the test is up. | [StatusCake](https://docs.evohub.io/statuscake.md) | | **Nagios** | `nagios` | Notification command that posts JSON with `curl`. | Yes, on a RECOVERY notification. | [Nagios](https://docs.evohub.io/nagios.md) | | **Cortex XDR** | `cortex` | Cortex XDR / XSIAM webhook forwarding. | Yes, when the issue is resolved. | [Cortex XDR](https://docs.evohub.io/cortex-xdr.md) | | **OpenSearch** | `opensearch` | Alerting notification channel (custom webhook). | No — resolve in EvoHub. | [OpenSearch](https://docs.evohub.io/opensearch.md) | | **Uptime Kuma** | `uptimekuma` | Webhook notification. | Yes, when the monitor is up. | [Uptime Kuma](https://docs.evohub.io/uptime-kuma.md) | | **Graylog** | `graylog` | HTTP Notification on event definitions. | No — resolve in EvoHub. | [Graylog](https://docs.evohub.io/graylog.md) | | **Splunk** | `splunk` | Webhook alert action. | No — resolve in EvoHub. | [Splunk](https://docs.evohub.io/splunk.md) | | **Cloudflare** | `cloudflare` | Notifications webhook destination. | Yes, when the alert ends or the health check is healthy. | [Cloudflare](https://docs.evohub.io/cloudflare.md) | | **IBM Instana** | `instana` | Webhook alert channel. | Yes, when the issue closes. | [IBM Instana](https://docs.evohub.io/ibm-instana.md) | | **Netdata** | `netdata` | Netdata Cloud webhook configuration. | Yes, when the alert clears or the node is reachable. | [Netdata](https://docs.evohub.io/netdata.md) | | **AWS EventBridge** | `eventbridge` | API destination or SNS topic; GuardDuty, Security Hub and AWS Health read natively. | Security Hub findings, AWS Health events and CloudWatch alarm state changes, yes; GuardDuty, no. | [AWS EventBridge](https://docs.evohub.io/aws-eventbridge.md) | | **AWS CloudTrail** | `cloudwatch` | Metric filter → CloudWatch alarm → SNS. | Yes, when the alarm returns to OK. | [AWS CloudTrail](https://docs.evohub.io/aws-cloudtrail.md) | | **Elastic / Kibana** | `elastic` | Webhook connector on alerting rules, with a body template. | Yes, from the rule's Recovered action. | [Elastic / Kibana](https://docs.evohub.io/elastic-kibana.md) | | **ManageEngine OpManager** | `opmanager` | "Invoke a Webhook" notification profile, with a body template. | Yes, when the alarm clears. | [ManageEngine OpManager](https://docs.evohub.io/manageengine-opmanager.md) | | **SolarWinds Orion** | `solarwinds` | POST request action on trigger and reset, with a body template. | Yes, on reset. | [SolarWinds Orion](https://docs.evohub.io/solarwinds.md) | | **Sumo Logic** | `sumologic` | Webhook connection, with a body template. | Yes, from the recovery payload. | [Sumo Logic](https://docs.evohub.io/sumo-logic.md) | | **AppDynamics** | `appdynamics` | HTTP Request Template on a policy action. | Yes, when the violation ends or is canceled. | [AppDynamics](https://docs.evohub.io/appdynamics.md) | | **Honeycomb** | `honeycomb` | Webhook integration as a trigger recipient. | Yes, when the trigger is OK. | [Honeycomb](https://docs.evohub.io/honeycomb.md) | | **Wazuh** | `wazuh` | Integrator custom integration with a script EvoHub provides. | No — resolve in EvoHub. | [Wazuh](https://docs.evohub.io/wazuh.md) | | **Checkmk** | `checkmk` | Notification script EvoHub provides. | Yes, on a RECOVERY notification. | [Checkmk](https://docs.evohub.io/checkmk.md) | | **Icinga** | `icinga` | Icinga Notifications webhook channel, or an Icinga 2 notification script EvoHub provides. | Yes, when the incident recovers or on a RECOVERY notification. | [Icinga](https://docs.evohub.io/icinga.md) | | **LibreNMS** | `librenms` | API alert transport, with a body template. | Yes, from the rule's recovery alert. | [LibreNMS](https://docs.evohub.io/librenms.md) | | **FortiGate** | `fortigate` | Automation stitch with a Webhook action. | No — resolve in EvoHub. | [FortiGate](https://docs.evohub.io/fortigate.md) | | **GitHub** | `github` | Repository or organization webhook, signed. | Yes, when the next run or deployment succeeds, or the alert is fixed. | [GitHub](https://docs.evohub.io/github.md) | | **GitLab** | `gitlab` | Project or group webhook with a secret token. | Yes, when the next pipeline, job or deployment succeeds. | [GitLab](https://docs.evohub.io/gitlab.md) | | **Jenkins** | `jenkins` | Notification plugin, or `curl` from a pipeline. | Yes, when the same job and branch builds successfully. | [Jenkins](https://docs.evohub.io/jenkins.md) | | **SonarQube** | `sonarqube` | Project or global webhook, signed. | Yes, when the quality gate passes again. | [SonarQube](https://docs.evohub.io/sonarqube.md) | | **KOI** | `koi` | KOI webhook; the mapping is provisional. | Yes, when the alert is resolved, closed, dismissed or remediated. | [KOI](https://docs.evohub.io/koi.md) | ### Not in the list? Three integrations accept alerts from anything: - **API** — JSON events to trigger, acknowledge and resolve alerts by `dedup_key`. It reads the Events API v2 format other paging tools use as it is (the same body, `dedup_key` and `202` answer), so a tool or script that already sends Events API v2 only needs the new URL and key. See [Alert API and generic webhook](https://docs.evohub.io/generic-webhook.md). - **Webhook** — any tool that can POST a JSON body, including flat `alert_*` fields. See [Generic alert webhook](https://docs.evohub.io/generic-webhook.md#generic-alert-webhook). - **Email** — any tool that can send an email. See [Email](https://docs.evohub.io/email-integration.md). Moving from another paging tool? The [Opsgenie](https://docs.evohub.io/migrate-from-opsgenie.md) and [PagerDuty](https://docs.evohub.io/migrate-from-pagerduty.md) importers create these integrations for you. Uptime monitors in EvoHub can also page On-Call directly, without an integration. See [Uptime alerts and silence windows](https://docs.evohub.io/uptime-alerts-and-silence-windows.md). For chat, see [Slack](https://docs.evohub.io/slack.md) and [Microsoft Teams](https://docs.evohub.io/microsoft-teams.md). ## How ingest works When a tool sends to an integration URL (the API and Email integrations answer slightly differently; see their pages): 1. EvoHub checks the key. A missing, unknown or disabled key is refused with `401` and the code `INVALID_KEY`. 2. The parser reads the tool's payload. A body the parser cannot read is refused with `400` and `INVALID_BODY`. 3. Each firing alert in the payload opens an alert (or matches an open one, see below), and the integration's escalation policy starts. 4. Each recovery in the payload resolves the matching open alert. The response is `200` with a count of what happened: ```json { "data": { "received": 1, "created": 1, "resolved": 0 }, "success": true } ``` A tool's own test notification (the **Test** button in Uptime Kuma, Graylog, Cloudflare, Instana, Netdata, Kibana, OpManager, SolarWinds, Sumo Logic, AppDynamics, Honeycomb and LibreNMS, GitHub's ping when a webhook is created, or the `--test` run of the Wazuh, Checkmk and Icinga 2 scripts) is answered with `200` and `"test": true` and opens no alert, so testing never pages anyone; the integration's **Last Event** still updates. Netdata is the one exception: its test carries a verification token you need, so it opens one **info** alert that pages nobody and does not count toward usage — see [Netdata](https://docs.evohub.io/netdata.md). Pingdom's test webhook and OpenSearch's **Send test message** work the other way: each opens a real alert under the integration's escalation policy, so the whole path, paging included, is exercised — see [Pingdom](https://docs.evohub.io/pingdom.md) and [OpenSearch](https://docs.evohub.io/opensearch.md). EvoHub answers `200` whenever the payload was read, even if it produced no alert, so that tools do not disable the webhook after repeated errors. If the key could not be checked on EvoHub's side, the answer is `503` with `INGEST_UNAVAILABLE` and a `Retry-After` header; most tools retry automatically. ### Deduplication Every alert gets a **fingerprint** from the tool's own identifiers — Alertmanager's fingerprint, a Datadog alert ID, a Zabbix host and trigger, a PRTG sensor ID, and so on. If an alert arrives while an alert with the same fingerprint is still open (triggered or acknowledged), EvoHub records **Retriggered** on the existing alert instead of opening a new one, and nobody is paged again. After the alert is resolved, the next one with that fingerprint opens a new alert. Only new alerts count toward usage; retriggers do not. For every integration that authenticates with the key in its URL, fingerprints are kept per integration: the same fingerprint arriving through two integrations opens two alerts, and a recovery only resolves alerts of the integration it arrived on. These integrations refuse bodies larger than 1 MiB with `413`, and answer `500` when an alert could not be stored, so the tool sends it again. Alerts opened before an integration's fingerprints became per-integration still resolve normally. ### Bodies that cannot be read Tools that fill in a body template you paste — Elastic / Kibana, ManageEngine OpManager, SolarWinds Orion, Sumo Logic, AppDynamics, LibreNMS and others — usually insert values without escaping them, so a line break, a double quote or a Windows path inside a value breaks the JSON. EvoHub repairs these. If a body that starts as a JSON object still cannot be read, it is **not refused**: EvoHub opens an alert titled * alert (payload could not be read)*, severity **high**, under the integration's escalation policy, with the body exactly as received in its description and the label `payload=unparsed`. Sending the same body again is recorded as **Retriggered**. Such an alert never resolves by itself; fix the template in the tool and resolve it in EvoHub. A body that is not a JSON object at all is refused with `400`. ### Auto-resolve When a tool reports that a problem has recovered, EvoHub resolves the open alert with the same fingerprint, and the escalation stops. The alert's timeline shows it was resolved by the integration. If the tool's recovery notification is not configured, alerts stay open until someone resolves them. ### Severity Each parser maps the tool's severity to EvoHub's **critical**, **high**, **medium**, **low** or **info**. The integration pages describe the mapping. When a tool sends no severity EvoHub recognizes, most parsers use **medium**; email alerts are always **high**. ## Related - [Alert API and generic webhook](https://docs.evohub.io/generic-webhook.md) - [New Relic](https://docs.evohub.io/new-relic.md), [AWS CloudWatch](https://docs.evohub.io/aws-cloudwatch.md), [Azure Monitor](https://docs.evohub.io/azure-monitor.md), [Dynatrace](https://docs.evohub.io/dynatrace.md), [Sentry](https://docs.evohub.io/sentry.md), [Google Cloud Monitoring](https://docs.evohub.io/google-cloud-monitoring.md) - [UptimeRobot](https://docs.evohub.io/uptimerobot.md), [Pingdom](https://docs.evohub.io/pingdom.md), [Site24x7](https://docs.evohub.io/site24x7.md), [StatusCake](https://docs.evohub.io/statuscake.md), [Nagios](https://docs.evohub.io/nagios.md), [Cortex XDR](https://docs.evohub.io/cortex-xdr.md), [OpenSearch](https://docs.evohub.io/opensearch.md), [KOI](https://docs.evohub.io/koi.md) - [Uptime Kuma](https://docs.evohub.io/uptime-kuma.md), [Graylog](https://docs.evohub.io/graylog.md), [Splunk](https://docs.evohub.io/splunk.md), [Cloudflare](https://docs.evohub.io/cloudflare.md), [IBM Instana](https://docs.evohub.io/ibm-instana.md), [Netdata](https://docs.evohub.io/netdata.md) - [AWS EventBridge](https://docs.evohub.io/aws-eventbridge.md), [AWS CloudTrail](https://docs.evohub.io/aws-cloudtrail.md), [Elastic / Kibana](https://docs.evohub.io/elastic-kibana.md), [ManageEngine OpManager](https://docs.evohub.io/manageengine-opmanager.md), [SolarWinds Orion](https://docs.evohub.io/solarwinds.md), [Sumo Logic](https://docs.evohub.io/sumo-logic.md), [AppDynamics](https://docs.evohub.io/appdynamics.md), [Honeycomb](https://docs.evohub.io/honeycomb.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) - [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md) - [Migrate from Opsgenie](https://docs.evohub.io/migrate-from-opsgenie.md) --- Source: https://docs.evohub.io/generic-webhook.md # Alert API and generic webhook If your tool is not in the catalog, or you want to raise alerts from your own code, use the **API** integration. It accepts a small JSON body in which only a summary is required, and it can also acknowledge and resolve alerts. Its body is compatible with the Events v2 shape used by other paging tools, so a tool that already speaks it only needs a new URL and key. ## Set up the API integration :::steps ### Create the integration Go to **On-Call → Integrations → + Add Integration**, choose **API**, choose an **Escalation Policy** and click **Create Integration**. ### Copy the URL and key Open the integration. The **Webhook URL** is `https://evohub.io/ingest/api?key=`; the **API Key** is the key on its own. ### Send a test event Run the `curl` example below with your key and check that an alert appears in **On-Call → Alerts**. ::: ## Send an event `POST https://evohub.io/ingest/api` with a JSON body. Send the integration key in whichever way your tool makes easiest: - `"routing_key"` in the body, - `?key=` in the URL, or - an `Authorization: Bearer ` header. :::code-group ```bash [Minimal trigger] curl -X POST 'https://evohub.io/ingest/api' \ -H 'Authorization: Bearer YOUR_INTEGRATION_KEY' \ -H 'Content-Type: application/json' \ -d '{"summary": "Database primary is down"}' ``` ```bash [Full trigger] curl -X POST 'https://evohub.io/ingest/api' \ -H 'Content-Type: application/json' \ -d '{ "routing_key": "YOUR_INTEGRATION_KEY", "event_action": "trigger", "dedup_key": "db-primary-down", "payload": { "summary": "Database primary is down", "source": "db-01.prod.acme.example", "severity": "critical", "component": "postgres", "group": "payments", "custom_details": { "region": "eu-central" } } }' ``` ```bash [Resolve] curl -X POST 'https://evohub.io/ingest/api' \ -H 'Content-Type: application/json' \ -d '{ "routing_key": "YOUR_INTEGRATION_KEY", "event_action": "resolve", "dedup_key": "db-primary-down" }' ``` ::: ### Request fields You can send the alert details nested in `payload` (Events v2 style) or as flat fields at the top level. A flat field fills the matching `payload` field when that one is empty. | Field | Required | Description | | --- | --- | --- | | `routing_key` | No* | The integration key. *Required unless sent as `?key=` or a bearer token. | | `event_action` | No | `trigger` (default), `acknowledge` or `resolve`. | | `dedup_key` | For acknowledge and resolve | Identifies the alert. On a trigger without one, EvoHub derives a key from the summary and source and returns it. | | `payload.summary` / `summary` / `title` | For trigger | The alert's title. | | `payload.severity` / `severity` | No | `critical`, `high`, `medium`, `low` or `info`. The Events v2 values are accepted too: `error` is read as `high` and `warning` as `medium`. Anything else, or nothing, is `medium`. | | `payload.source` / `source` | No | Where the problem is, for example a host name. Stored as the `source` label. | | `description` / `payload.description` | No | The alert's description. | | `payload.component`, `payload.group`, `payload.class` | No | Stored as labels of the same name. | | `payload.custom_details` | No | An object; each entry becomes a label, its value as text. | | `client_url` | No | The alert's link back to the sending tool. It opens from the alert. | | `links` | No | A list of `{"href", "text"}`, as in Events v2. Up to 10 are kept, each under its `text` (or `link_1`, `link_2`…), and open from the alert. Entries without an `href` are skipped. | | `payload.timestamp`, `client` | No | Accepted for compatibility and ignored. The alert is stamped when it arrives. | ### Response A processed event returns `202`: ```json { "status": "success", "message": "Event processed", "dedup_key": "db-primary-down", "data": { "status": "success", "message": "Event processed", "dedup_key": "db-primary-down" }, "success": true } ``` The same fields appear at the top level, where Events v2 clients read them, and under `data`. Keep the `dedup_key` if you let EvoHub derive it — you need it to resolve the alert later. A request that was not processed returns the same envelope with `"status": "invalid event"` and a `message`: | Status | When | Example `message` | | --- | --- | --- | | `400` | The body is not JSON, a trigger has no summary, an acknowledge or resolve has no `dedup_key`, or `event_action` is unknown. | `summary is required` | | `401` | No key was sent, or it names no enabled integration. | `invalid routing_key` | | `500` | The alert could not be created, acknowledged or resolved. | `failed to create alert` | | `503` | The key could not be checked right now. Retry after the `Retry-After` seconds. | `could not verify routing_key right now; retry later` | ### How events behave - **Trigger** opens an alert and starts the integration's escalation policy. A trigger whose `dedup_key` matches an alert that is still open does not open a second one; it is recorded as **Retriggered** on the existing alert. - **Acknowledge** acknowledges the open, triggered alert with that `dedup_key`. - **Resolve** resolves the open alert with that `dedup_key`. - Acknowledging or resolving a `dedup_key` that has no open alert does nothing and still returns `202`. - The integration's escalation policy always applies; the request cannot choose another one. ## Generic alert webhook EvoHub also accepts a simpler, flat alert format at `/ingest/alerts`. Create a **Webhook** integration for it (the key of an **API** integration works too): :::code-group ```bash [Trigger] curl -X POST 'https://evohub.io/ingest/alerts?key=YOUR_INTEGRATION_KEY' \ -H 'Content-Type: application/json' \ -d '{ "title": "Disk almost full on web-03", "description": "/var is at 93%", "severity": "high", "fingerprint": "web-03-disk-var", "labels": { "host": "web-03.acme.example" }, "annotations": { "runbook": "https://wiki.acme.example/disk" } }' ``` ```bash [Resolve] curl -X POST 'https://evohub.io/ingest/alerts?key=YOUR_INTEGRATION_KEY' \ -H 'Content-Type: application/json' \ -d '{ "fingerprint": "web-03-disk-var", "status": "resolved" }' ``` ::: | Field | Required | Description | | --- | --- | --- | | `title` | To open an alert | The alert's title. | | `description` | No | The alert's description. | | `severity` | No | `critical`, `high`, `medium`, `low` or `info`, in any letter case. Common synonyms are read too: `error` and `major` as `high`, `warning` as `medium`, `minor` as `low`, and `p1`–`p5` / `sev1`–`sev5` in order. Anything else, or nothing, is `medium`. | | `fingerprint` | No | Identifies the alert within this integration. Repeats while it is open are recorded as **Retriggered**. Without one, EvoHub derives it from the title, the instance, every label and `alert_service` — see [Alerts without a fingerprint](#alerts-without-a-fingerprint). | | `status` | No | `resolved`, `recovered`, `closed`, `ok`, `online` or `up` (any letter case) resolves the open alert with the same fingerprint (or, without one, the same title, instance, labels and `alert_service`) instead of opening one. | | `source` | No | Kept as the `source` label. The alert's source is always **webhook**. | | `labels`, `annotations` | No | String-to-string maps, shown on the alert and usable in the voice template. | | `escalation_policy_id` | No | Used only when the integration has no escalation policy of its own. | The key goes in `?key=` only. A missing `title` (on a trigger), or a resolve with neither a `fingerprint` nor a `title`, returns `400` with the code `VALIDATION_FAILED` and the field in `details`; a body that is not a JSON object returns `400` with `INVALID_BODY`; a bad key returns `401` with `INVALID_KEY`. A body larger than 1 MiB returns `413` with `PAYLOAD_TOO_LARGE`. A successful call returns `200` with `{"data": {"received": 1, "created": 1, "resolved": 0}, "success": true}`; `resolved` is the number of alerts actually resolved, so a resolve that matched no open alert answers `200` with `"resolved": 0`. This format cannot acknowledge; use the API events above for that. ### Alerts without a fingerprint When a body has no `fingerprint`, EvoHub derives one from the title, the instance (`alert_instance` or the `instance` label), every label as `key=value`, and `alert_service`. Description, severity, `alert_date` and annotations are not part of it. - Sending exactly the same alert again retriggers the open alert instead of opening a second one. (Before October 2026 every such send opened a new alert.) - Alerts that differ in any label — another `host`, another `region` — are separate alerts. - A resolve without a `fingerprint` must carry the same title, instance, labels and `alert_service` the alert was opened with. If you want to choose the grouping yourself, send a `fingerprint`. ### Fingerprints belong to the integration A fingerprint, sent or derived, only matches alerts opened through the same integration. Two integrations in one organization that send the same fingerprint open separate alerts, and neither can resolve the other's. EvoHub stores the fingerprint prefixed with the integration's ID; send it to this webhook as you always have, without the prefix. ### Limits Titles are kept to 250 characters and descriptions to 4,000. At most 50 labels and 50 annotations are kept (the first 50 by key), with keys up to 64 characters and values up to 512. Longer text is cut, not refused, so the alert still opens. ### Flat `alert_*` fields The generic webhook also reads a flat set of `alert_*` field names. If a tool's webhook template already sends these fields, point it at your EvoHub webhook URL and leave the body as it is. | Field | Becomes | | --- | --- | | `alert_name` | Title | | `alert_description` | Description | | `alert_severity` | Severity (`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, `INFO` in any case) | | `alert_instance` | The `instance` label | | `alert_service` | The `service` label. On-Call does not route by service; use the integration's escalation policy for that. | | `alert_date` | The `date` label. The alert is stamped when it arrives. | | `alert_status` | `resolved` resolves the alert with the same `alert_name`, `alert_instance` and `alert_service` (and labels, if you send any). | ```bash curl -X POST 'https://evohub.io/ingest/alerts?key=YOUR_INTEGRATION_KEY' \ -H 'Content-Type: application/json' \ -d '{ "alert_name": "High CPU", "alert_severity": "HIGH", "alert_instance": "es-01", "alert_service": "Elasticsearch", "alert_description": "CPU above 95% for 10 minutes", "alert_date": "10.10.2026" }' ``` Send the same body with `"alert_status": "resolved"` to resolve it. When a body carries both an EvoHub field and its `alert_*` counterpart (for example `title` and `alert_name`), the EvoHub field wins. ## Tips - Choose a stable `dedup_key` (or `fingerprint`) per problem, such as `-`, so repeats while the problem lasts do not open new alerts and your resolve finds the right alert. - Send a resolve when your check passes again. Without one, the alert stays open until someone resolves it. - Only new alerts count toward usage; retriggers of an open alert do not. See [How billing works](https://docs.evohub.io/how-billing-works.md). ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/prometheus-alertmanager.md # Prometheus EvoHub receives alerts from Prometheus through Alertmanager's webhook receiver. Each firing alert opens an EvoHub alert, and when Alertmanager reports it resolved, EvoHub resolves it too. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Prometheus**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a receiver to Alertmanager Open your Alertmanager configuration (`alertmanager.yml`) and add a receiver that uses the URL. Keep `send_resolved: true` so EvoHub can resolve alerts automatically. ### Route alerts to it Point a route at the `evohub` receiver — the top-level route, or a sub-route for the alerts that should page — and reload Alertmanager. ::: ```yaml route: receiver: evohub # Or keep your current default receiver and add a sub-route: # routes: # - receiver: evohub # matchers: # - severity=~"critical|warning" receivers: - name: evohub webhook_configs: - url: "https://evohub.io/ingest/prometheus?key=YOUR_INTEGRATION_KEY" send_resolved: true ``` To keep another destination during a migration, add a second route or receiver for it; Alertmanager can notify several receivers. ## What EvoHub reads Alertmanager sends a group of alerts in one request. EvoHub handles each alert in the group separately: | EvoHub alert | Taken from | | --- | --- | | Title | The `alertname` label (or "Prometheus Alert" if it is missing). | | Description | The `summary` annotation, or the `description` annotation if there is no summary. | | Severity | The `severity` label — see below. | | Labels and annotations | All of the alert's labels and annotations, shown on the alert and usable in the voice template. | | Link | `generatorURL`, the expression that fired, as `url`. A `url` annotation on the rule wins. It opens from the alert. | | Fingerprint | Alertmanager's fingerprint for the alert (or one computed from its labels). | ### Severity | `severity` label | EvoHub severity | | --- | --- | | `critical`, `page`, `emergency`, `fatal`, `p1` | critical | | `high`, `error`, `major`, `warning`, `p2` | high | | `low`, `minor`, `p4` | low | | `info`, `informational`, `none`, `p5` | info | | anything else (including `medium`), or missing | medium | Add a `severity` label to your alerting rules to control how urgent each alert is. ## Resolve and deduplication - An alert with status `firing` opens an EvoHub alert. If the same alert (same fingerprint) is still open in EvoHub, Alertmanager's repeat notifications are recorded as **Retriggered** and nobody is paged again. - An alert with status `resolved` resolves the matching EvoHub alert. This needs `send_resolved: true`. - Because the fingerprint comes from the alert's labels, an alert whose labels change (for example a label with a changing value) is treated as a new alert. Keep frequently changing values in annotations, not labels. ## Test it Send a test alert to Alertmanager with `amtool`: ```bash amtool alert add alertname=EvoHubTest severity=critical \ --annotation=summary="Test alert from Alertmanager" \ --alertmanager.url=http://localhost:9093 ``` After Alertmanager's group wait, the alert appears in **On-Call → Alerts** and the integration's **Last Event** updates. ## Troubleshooting - **Nothing arrives**: check Alertmanager's logs for webhook errors. A `401` means the key in the URL is wrong or the integration is disabled. - **Alerts never resolve**: make sure `send_resolved: true` is set on the webhook config. - **Alert arrives but nobody is paged**: check that the integration has an escalation policy and that someone is on call on the schedule it targets. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Grafana](https://docs.evohub.io/grafana.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/grafana.md # Grafana EvoHub receives alerts from Grafana Alerting through a **Webhook** contact point. Each firing alert opens an EvoHub alert, and Grafana's resolved notification resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Grafana**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a contact point in Grafana In Grafana Alerting, open **Contact points** (under **Notification configuration** in recent versions) and click **+ Add contact point**. Choose the **Webhook** integration, paste the URL into **URL** and set **HTTP Method** to **POST**. Save. ### Use it in a notification policy In Grafana Alerting's **Notification policies**, send the alerts that should page to the new contact point — as the default policy or a nested policy matching specific labels. ::: Grafana sends resolved notifications by default, so auto-resolve works without extra settings (unless you turned on **Disable resolved message** on the contact point). You can use Grafana's **Test** button on the contact point to check the connection; it sends a firing test notification that EvoHub treats like any other alert: it opens an alert under the integration's escalation policy, so it pages, and you resolve it in EvoHub. ## What EvoHub reads Grafana can send several alerts in one notification. EvoHub handles each alert separately: | EvoHub alert | Taken from | | --- | --- | | Title | The alert's `alertname` label, otherwise the notification title. | | Description | The `summary` annotation, otherwise the `description` annotation, otherwise the notification message. | | Severity | The `severity` label — see below. | | Labels and annotations | All of the alert's labels and annotations. | | Links | The rule (`url`), and the dashboard, panel, silence and screenshot links when Grafana sends them (`dashboard_url`, `panel_url`, `silence_url`, `image_url`). They open from the alert. | | Values | What crossed the threshold, as `values` (for example `A=97.2, B=1`). | | Fingerprint | Grafana's fingerprint for the alert (or one computed from its labels). | ### Severity Add a `severity` label to your Grafana alert rules to control urgency: | `severity` label | EvoHub severity | | --- | --- | | `critical`, `page`, `emergency`, `fatal`, `p1` | critical | | `high`, `error`, `major`, `warning`, `p2` | high | | `low`, `minor`, `p4` | low | | `info`, `informational`, `none`, `p5` | info | | anything else (including `medium`), or missing | medium | ## Resolve and deduplication - A `firing` alert opens an EvoHub alert. Repeat notifications for an alert that is still open are recorded as **Retriggered**, and nobody is paged again. - A `resolved` alert resolves the matching EvoHub alert. - Alerts with different label sets are different alerts. A multi-dimensional rule (for example one alert per host) opens one EvoHub alert per host. ## Troubleshooting - **Contact point test fails with 401**: the key in the URL is wrong, or the integration is disabled in EvoHub. - **Alerts never resolve**: check that resolved messages are not disabled on the contact point. - **Alert arrives but nobody is paged**: check the integration's escalation policy and who is on call. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Prometheus](https://docs.evohub.io/prometheus-alertmanager.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/datadog.md # Datadog EvoHub receives Datadog monitor notifications through Datadog's **Webhooks** integration. A triggered monitor opens an EvoHub alert, and its recovery notification resolves it. Datadog's default payload works as it is; an optional custom payload gives EvoHub better severity and a stable alert ID. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Datadog**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a webhook in Datadog In Datadog, go to **Integrations → Webhooks** and add a new webhook. Set **Name** to `evohub` and **URL** to the webhook URL. Save. ### Mention it in your monitors In each monitor's notification message, add `@webhook-evohub`. Datadog then notifies EvoHub when the monitor triggers and when it recovers. ::: ## Optional: custom payload With the default payload, EvoHub reads the monitor's state from the notification title (for example `[Triggered]` or `[Recovered]`) and every alert is **medium** severity. For proper severity, tags as labels and a stable alert ID, turn on **Use custom payload** on the webhook and paste: ```json { "id": "$ID", "title": "$ALERT_TITLE", "body": "$EVENT_MSG", "priority": "$ALERT_PRIORITY", "alert_type": "$ALERT_TYPE", "alert_status": "$ALERT_TRANSITION", "alert_id": "$ALERT_ID", "alert_scope": "$ALERT_SCOPE", "tags": "$TAGS", "link": "$LINK" } ``` The `$…` parts are Datadog template variables that Datadog fills in. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `title`, with any `[Triggered on {…}]` prefix removed. A scope such as `{host:web-1}` in the prefix is kept as a `scope` label. | | Description | `body`. | | Severity | `alert_type` and `priority` — see below. | | Labels | `tags`, split into `key:value` pairs; `alert_scope`, when sent, as the `scope` label. | | Link | `link` (`$LINK`), the event in Datadog, when sent. It opens from the alert. | | Fingerprint | `alert_id` together with `alert_scope` when both are present; `alert_id` alone when there is no scope; otherwise derived from the title (and scope), which is the same on the trigger and recovery notifications. | ### Severity | `alert_type` | `priority` | EvoHub severity | | --- | --- | --- | | `error` | `P1`, `P2` or `normal` | critical | | `error` | anything else | high | | `warning` | any | medium | | `info`, `success` | any | info | | missing (default payload) | any | medium | ## Resolve and deduplication - A notification whose `alert_status` (custom payload) or title prefix (default payload) says **Recovered** resolves the matching alert. Nothing is opened. - Any other notification — triggered, re-triggered, warn, no data, renotify — opens an alert, or is recorded as **Retriggered** on the alert that is still open for the same monitor and scope. - Multi-alert monitors: with the default payload, each group (for example each host) gets its own EvoHub alert, because the scope is part of the fingerprint. With the custom payload above, `alert_scope` (`$ALERT_SCOPE`) does the same, so each group gets its own alert and resolves on its own. A custom payload without `alert_scope` keys on `$ALERT_ID` alone: every group shares one EvoHub alert, and the first group to recover resolves it. ## Troubleshooting - **Nothing arrives**: check that the monitor message contains `@webhook-evohub` and that the webhook URL includes `?key=`. - **Recoveries open new alerts instead of resolving**: use the custom payload so EvoHub gets a stable `alert_id`, or make sure the monitor title does not change between trigger and recovery. - **Every alert is medium**: switch to the custom payload above. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) - [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md) --- Source: https://docs.evohub.io/zabbix.md # Zabbix EvoHub receives Zabbix problems through a **Webhook** media type with a short script. A problem opens an EvoHub alert, and the recovery message resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Zabbix**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create a webhook media type In Zabbix, go to **Alerts → Media types → Create media type** and choose type **Webhook**. ### Add the parameters Add these parameters (**Name** = **Value**), using your webhook URL for `url`. ### Paste the script Paste the script below into the **Script** field and save the media type. ### Assign the media type Add the media type to the Zabbix user that your trigger actions notify (the user's **Media** tab), and make sure an action sends problems to that user. ::: Parameters: ```text url = https://evohub.io/ingest/zabbix?key=YOUR_INTEGRATION_KEY event_id = {EVENT.ID} trigger_name = {EVENT.NAME} trigger_severity = {EVENT.SEVERITY} trigger_status = {EVENT.STATUS} host_name = {HOST.NAME} host_ip = {HOST.IP} event_value = {EVENT.VALUE} opdata = {EVENT.OPDATA} zabbix_url = {$ZABBIX.URL} trigger_id = {TRIGGER.ID} ``` `opdata`, `zabbix_url` and `trigger_id` are optional. With them the alert shows what was measured and links to the event in Zabbix. `{$ZABBIX.URL}` is Zabbix's own global macro; set it under **Administration → Macros** to your frontend's address, for example `https://zabbix.example.com`. Script: ```javascript var p = JSON.parse(value); var req = new HttpRequest(); req.addHeader('Content-Type: application/json'); req.post(p.url, JSON.stringify({ event_id: p.event_id, trigger_name: p.trigger_name, trigger_severity: p.trigger_severity, trigger_status: p.trigger_status, host_name: p.host_name, host_ip: p.host_ip, event_value: p.event_value, opdata: p.opdata, zabbix_url: p.zabbix_url, trigger_id: p.trigger_id })); if (req.getStatus() < 200 || req.getStatus() >= 300) throw 'EvoHub ' + req.getStatus(); return 'OK'; ``` ## Turn on auto-resolve Zabbix only sends a recovery if you ask it to: 1. In the media type, open **Message templates** and add a **Problem recovery** template. Without it, Zabbix never sends a recovery and alerts stay open. 2. In your trigger action, open **Recovery operations** and send a message through this same media type. No script or parameter change is needed: the recovery carries `event_value = 0` and EvoHub resolves the matching alert. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `trigger_name` (or "Zabbix Alert"). | | Description | "Host: " and `host_name`, then "Value: " and `opdata` when sent. | | Labels | `host`, `severity` and, when sent, `host_ip`. | | Link | The event in Zabbix, built from `zabbix_url`, `trigger_id` and `event_id` when all three are sent. It opens from the alert. | | Fingerprint | The host name and trigger name together. | ### Severity | Zabbix severity | EvoHub severity | | --- | --- | | Disaster | critical | | High | high | | Average | medium | | Warning | low | | Information, Not classified | info | ## Resolve and deduplication - A message is a recovery when `event_value` is `0` or `trigger_status` is `RESOLVED` or `OK`. It resolves the open alert for the same host and trigger. - Because the fingerprint is host plus trigger name, repeated notifications for the same problem are recorded as **Retriggered** on the open alert, and the same trigger on different hosts opens separate alerts. ## Troubleshooting - In Zabbix, **Reports → Action log** shows each media type call. An error such as `EvoHub 401` means the key is wrong or the integration is disabled. - If alerts never resolve, check that the **Problem recovery** template exists and that the action has a recovery operation using this media type. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [PRTG](https://docs.evohub.io/prtg.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/prtg.md # PRTG EvoHub receives PRTG sensor alerts through a notification template with **Execute HTTP Action**. A sensor going Down or Warning opens an EvoHub alert, and the sensor returning to Up resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **PRTG**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create a notification template In PRTG, go to **Setup → Account Settings → Notification Templates**, add a notification template and enable **Execute HTTP Action**. ### Set the URL and method Set **URL** to your webhook URL and **HTTP Method** to **POST**. GET sends no payload. ### Set the postdata Paste the postdata below as it is. PRTG fills in the `%…` placeholders. ### Use it in triggers Add the template to the notification triggers of the sensors (or groups and devices) that should page: a **State Trigger** for Down and Warning, and for Up so alerts resolve. ::: Postdata: ```text device=%device&sensor=%sensor&status=%laststatus&message=%message&priority=%priority&sensor_id=%sensorid&link=%linksensor&lastvalue=%lastvalue ``` PRTG's HTTP action sends form-encoded data, which EvoHub reads directly. EvoHub also accepts the same fields as JSON. > [!WARNING] > Keep `sensor_id=%sensorid` in the postdata: EvoHub matches a recovery to its alert by the sensor ID. Without it, EvoHub falls back to the device and sensor names, so renaming a sensor while it is down leaves its alert open. Keep `priority=%priority` too, so the sensor's stars set the severity; without it a Down sensor is **high**. `link` and `lastvalue` are optional: the alert then links to the sensor in PRTG and shows its last reading. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | "`sensor` on `device` is `status`", for example "Ping on db-01 is Down". | | Description | `message`. | | Labels | `device`, `sensor`, `status`. | | Fingerprint | `sensor_id`; without it, the device and sensor names. | | Link and value | `link` (`%linksensor`) opens the sensor in PRTG; `lastvalue` (`%lastvalue`) is shown as `last_value`. Both optional. | ### Severity Severity comes from the sensor's status and its priority (PRTG's `%priority` renders as stars, `*` to `*****`; a digit 1–5 also works): | Status | Priority | EvoHub severity | | --- | --- | --- | | Down | 5 stars | critical | | Down | 4 stars | high | | Down | 3 stars | medium | | Down | 2 stars | low | | Down | 1 star | info | | Down | missing | high | | Warning | any | medium | | any other status (except Up and Paused) | any | low | Raise the priority of sensors whose failure should wake someone up. ## Resolve and deduplication - A notification whose status is **Up** or **Paused** resolves the open alert for that sensor. EvoHub reads the current state from both `%laststatus` ("Down") and the longer `%status` form ("Up ended (now: Down)"). - Repeated Down or Warning notifications for a sensor whose alert is still open are recorded as **Retriggered**. ## Troubleshooting - **Nothing arrives**: check that **HTTP Method** is POST and that the URL includes `?key=`. A `400` means the postdata has none of `device`, `sensor` or `sensor_id`. - **Recoveries do not resolve after a sensor was renamed**: add `sensor_id=%sensorid` to the postdata, so EvoHub keys on the sensor ID instead of its name. - **Alerts stay open**: add the template to the trigger for the Up state too. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Zabbix](https://docs.evohub.io/zabbix.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/email-integration.md # Email integration Some tools can only send email. The **Email** integration gives you a unique inbound address: every email sent to it opens an alert, and a follow-up email saying the problem recovered resolves it. No webhook is needed. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Email**, pick an **Escalation Policy** and click **Create Integration**. ### Copy the inbound address EvoHub shows "Integration created! Send alert emails to this address:" with the address. You can copy it again later from the integration's **Inbound email address** field. ### Use it as the alert recipient In your monitoring tool, add the address as the recipient of its alert emails. ### Send a test Send a test email from the tool (or from your own mailbox) and check that an alert appears in **On-Call → Alerts**. ::: Each email integration has its own address, so you can create one per tool and route each to a different escalation policy. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | The email subject (or "Email alert" if empty). | | Description | The plain-text body. For HTML-only emails, the text with the HTML tags removed. | | Severity | Always **high**. Email carries no reliable severity, so EvoHub does not guess one. | | Fingerprint | The subject in lower case, with status words (such as DOWN, UP, ALERT, RESOLVED, CRITICAL, IS, BACK) and brackets, parentheses, colons and hyphens removed. Status words are removed only as whole words, so "Backup" and "this" stay as they are. Each email integration keeps its own fingerprints. | ## Auto-resolve An email is treated as a recovery when its subject or body contains any of these phrases as whole words (case does not matter): `resolved`, `recovered`, `is up`, `back up`, `is back`, `cleared`, `has recovered`, `up again`, `ok again`, and `no longer` followed by `down`, `failing`, `firing`, `alerting`, `critical`, `in alarm`, `unhealthy` or `degraded` Whole words only: "unresolved", "this backend" or "is upgraded" do not count, and neither does a plain "no longer responding". A recovery email does not open an alert. It resolves the open alert of the same integration whose fingerprint matches — so `[DOWN] api.acme.example` is resolved by `[UP] api.acme.example is back up`, because both subjects reduce to the same fingerprint. > [!WARNING] > The recovery check looks at the body as well as the subject. If your tool's problem emails contain one of those phrases in their body (for example a footer such as "This alert will be resolved automatically"), they are read as recoveries and no alert opens. Remove the phrase from the tool's email template, or use a webhook integration for that tool. ## Deduplication While an alert is open, more emails to the same integration with the same subject (after status words are removed) are recorded as **Retriggered** on that alert instead of opening new ones. The same subject sent to two different email integrations opens two separate alerts. Within one integration, different monitors should have different subjects, for example by including the monitor or host name. ## Security - The address contains a random token and is not guessable. EvoHub does not check the sender, so anyone who knows the address can open alerts. Share it only with the tools that need it. - To stop accepting email, **Disable** the integration. To retire an address, **Delete** the integration and create a new one. - Emails to an address that does not belong to an enabled integration are ignored. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Alert API and generic webhook](https://docs.evohub.io/generic-webhook.md) - [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md) --- Source: https://docs.evohub.io/slack.md # Slack Connecting Slack lets EvoHub post On-Call alerts to your channels and lets your team act on them from Slack. Slack runs alongside paging: it never replaces the phone calls, push notifications and emails your escalation policies send. ## What the Slack integration does - **Posts new alerts to a default channel.** Every new alert in your organization is posted to the channel you choose. - **Posts from escalation steps.** Any escalation step can also post the alert to a channel when it runs, in addition to paging its target. - **Keeps each message up to date.** When an alert is acknowledged, taken over, assigned, redirected or resolved — in EvoHub, in the mobile app, by phone or in Slack — EvoHub edits the messages it already posted instead of posting new ones. - **Lets people respond from Slack.** Each message has **Acknowledge** (while the alert is triggered), **Resolve** (until it is resolved), a **…** menu with **Take over** and **Add note**, and **View in EvoHub**. Each message shows the alert's status and severity, its title, the start of its description, and **Source**, **Started** and **Assigned to**. Slack is free: posting messages and button presses are not metered. ## Connect your workspace :::steps ### Open Chat apps Click your avatar and go to **Organization → Chat apps**. The **Slack** card is there. ### Add to Slack Click **Add to Slack**, choose your Slack workspace and approve the EvoHub app. You return to Chat apps with "Slack connected". ### Invite the app to your channels In each Slack channel EvoHub should post to, type `/invite @EvoHub`. EvoHub can only list and post to channels the app has been added to. ### Choose the default channel Under **Post every new On-Call alert to**, pick the channel. If it is not listed, invite the app and click **Refresh**. Choose **None** to stop posting every new alert. ::: Connecting, disconnecting and choosing the default channel need the On-Call settings permission and the Organization settings permission. If the card says **Not available**, Slack cannot be connected for your organization at the moment. Once Slack is connected, its channels can also be picked as Uptime notification channels and as changelog notification destinations. See [Uptime alerts](https://docs.evohub.io/uptime-alerts-and-silence-windows.md) and [Reaching readers](https://docs.evohub.io/reaching-readers.md). ## Post from an escalation step To post an alert to a team's channel only when a particular step runs: 1. Open the escalation policy and click **Edit**. 2. In the step's **Chat** area, click **Send to Slack** and choose a channel. 3. Click **Save Changes**. The step still pages its target; the channel post is in addition. To stop posting, remove the channel from the step. See [Escalation policies](https://docs.evohub.io/escalation-policies.md). ## Respond from Slack When someone clicks a button, EvoHub acts as that person's EvoHub account: - EvoHub reads the email address on the person's Slack profile and looks for an EvoHub member of your organization with the same email. - That member must be allowed to respond to alerts. - The action is recorded on the alert's timeline under their name, marked as coming from Slack (for example "Acknowledged from Slack."). Notes added with **Add note** go on the timeline with "(from Slack)". If the action cannot be carried out, Slack shows the person a message explaining why, for example: - "Your Slack email (…) is not a member of the EvoHub organization this workspace is connected to." — invite them to EvoHub with that email. - "You are not allowed to respond to alerts in EvoHub." — give them a role that can respond to alerts. - "This alert is already resolved." ## Disconnect In **Chat apps → Slack**, click **Disconnect** and confirm. EvoHub stops posting alerts to Slack, and buttons on existing messages stop working. Channels chosen on escalation steps are kept for when you reconnect. The default channel is not: after reconnecting, choose it again under **Post every new On-Call alert to**. ## Related - [Escalation policies](https://docs.evohub.io/escalation-policies.md) - [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md) - [Integrations overview](https://docs.evohub.io/integrations-overview.md) --- Source: https://docs.evohub.io/uptime-kuma.md # Uptime Kuma EvoHub receives Uptime Kuma notifications through its built-in **Webhook** notification type. A monitor going down opens an EvoHub alert, and the monitor coming back up resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Uptime Kuma**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a Webhook notification In Uptime Kuma, go to **Settings → Notifications → Setup Notification** and set **Notification Type** to **Webhook**. ### Set the URL and body Set **Post URL** to your webhook URL and **Request Body** to **Preset - application/json**. (**Preset - multipart/form-data** works too.) ### Test it Click **Test**. EvoHub answers with success and opens no alert; the integration's **Last Event** updates. ### Enable it on monitors Turn the notification on for the monitors that should page, or tick **Default enabled** and **Apply on all existing monitors**. ::: ## What EvoHub reads Uptime Kuma posts `{"heartbeat": {...}, "monitor": {...}, "msg": "..."}`: | EvoHub alert | Taken from | | --- | --- | | Title | "`monitor.name` is down", for example "Checkout API is down". | | Description | `heartbeat.msg`, the reason the check failed. | | Severity | Always **high**. | | Labels | `monitor_type`, `monitor_id`, `url` (the monitored URL), `hostname`. | | Fingerprint | The monitor's ID (or, if a notification has none, its URL, hostname or name). | ## Resolve and deduplication - A heartbeat with status **down** opens an alert; status **up** resolves the open alert for that monitor. - **Pending** and **maintenance** heartbeats are ignored. - Another down notification while the alert is open is recorded as **Retriggered**. - Deduplication is per integration: two Uptime Kuma servers connected through two integrations open separate alerts even when their monitor IDs match, and each one's up notification resolves only its own alert. ## Troubleshooting - **Test works but no alerts arrive**: the notification is not enabled on the monitor. - **`400 INVALID_BODY`, or every notification answered as a test**: the request body was set to a custom template, which lacks the `heartbeat` and `monitor` objects EvoHub reads. Use one of the presets. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/graylog.md # Graylog EvoHub receives Graylog alerts through an **HTTP Notification** attached to your event definitions. Each event opens an EvoHub alert. Graylog does not send a notification when the condition clears, so these alerts are resolved in EvoHub. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Graylog**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create the notification In Graylog, go to **Alerts → Notifications → Create Notification**, choose **HTTP Notification** and set **URL** to your webhook URL. ### Test it Click **Execute Test Notification**. EvoHub answers with success and opens no alert; the integration's **Last Event** updates. ### Attach it to event definitions Go to **Alerts → Event Definitions**, edit each definition that should page, and add the notification on the **Notifications** step. ::: Graylog's fixed JSON body is read as it is; there is nothing to template. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `event_definition_title`. | | Description | `event.message`, followed by the first three backlog messages. | | Labels | Every field in `event.fields`, plus `event_key` and `event_definition_id`. | | Fingerprint | The event definition ID and `event.key` (the group-by value). | ### Severity | Graylog priority | EvoHub severity | | --- | --- | | 4 (critical) | critical | | 3 (high) | high | | 2 (normal) | medium | | 1 (low) | low | | 0 (info) | info | ## Resolve and deduplication - Graylog sends no recovery notification. Resolve the alert in EvoHub when the problem is fixed. - Each firing of the same event definition with the same group-by key, while the alert is open, is recorded as **Retriggered**. Use **Group by** fields in the event definition to get one alert per host or service. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Splunk](https://docs.evohub.io/splunk.md) --- Source: https://docs.evohub.io/splunk.md # Splunk EvoHub receives Splunk alerts through the **Webhook** alert action. Each time an alert triggers, EvoHub opens an alert with the first result row. Splunk does not send a notification when the condition clears, so these alerts are resolved in EvoHub. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Splunk**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Allow the URL (Splunk 9.0 and later) Add your webhook URL to Splunk's webhook allow list. Splunk refuses to call a URL that is not on it. ### Add the action Open the saved search, choose **Edit Alert**, and under **Trigger Actions** add **Webhook** with your webhook URL. ::: Splunk's webhook body is read as it is: ```json { "search_name": "Failed root logins", "result": { "host": "web-07", "count": "8" }, "sid": "scheduler_admin_search_W2_at_14232356_132", "results_link": "https://splunk.example.com/app/search/@go?sid=...", "owner": "admin", "app": "search" } ``` ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `search_name`, plus " on `host`" when the result has a `host` field. | | Description | The first result row as `field: value` lines (internal fields other than `_raw` are left out). | | Labels | `app`, `owner`, `host`, `sid`, and `url` (the `results_link`). | | Fingerprint | The search name, plus `host` when present. | ### Severity If the result row has a `severity` field (or, failing that, `urgency` or `priority`), EvoHub uses it: `critical`, `high`, `medium`, `low` or `info` (`error` reads as high, `warning` as medium); a value it does not recognize is medium. Set it in the search, for example: ```text ... | eval severity="critical" ``` Without one, Splunk alerts are **high**. ## Resolve and deduplication - Splunk sends no recovery notification. Resolve the alert in EvoHub when the problem is fixed. - Each run of the same search for the same host, while the alert is open, is recorded as **Retriggered**. The search ID changes every run and is not used for matching. - Splunk has no test button. A `POST` with an empty JSON object (`{}`) is answered as a test and opens no alert. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Graylog](https://docs.evohub.io/graylog.md) --- Source: https://docs.evohub.io/cloudflare.md # Cloudflare EvoHub receives Cloudflare notifications through a **webhook destination**. Any notification policy that uses the destination opens an EvoHub alert. Alerts with a start and an end, such as DDoS attacks, resolve when Cloudflare sends the end event. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Cloudflare**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create the destination In the Cloudflare dashboard, go to **Notifications → Destinations → Webhooks → Create**. Name it, set **URL** to your webhook URL and leave **Secret** empty — the key in the URL authenticates the call. ### Test and save Save and test the webhook. Cloudflare's "Hello World" test is answered with success and opens no alert. ### Use it in notifications Go to **Notifications → All Notifications → Add**, pick the alert type and select the webhook destination. ::: ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `policy_name` (or `name`). | | Description | `text`, Cloudflare's description of the notification. | | Labels | `alert_type`, `account_id`, `policy_id`, the alert's `data` fields (zone, hostname, attack type and so on), and `url` when the data carries a dashboard link. | | Fingerprint | `alert_correlation_id`; for health checks the health check ID; otherwise the policy and alert type. | ### Severity | Alert type | EvoHub severity | | --- | --- | | Contains `ddos`, `origin` or `health_check`, or starts with `dos_attack` (the standard HTTP and L3/4 DDoS alerts) | high | | Anything else | medium | ## Resolve and deduplication - `alert_event: ALERT_STATE_EVENT_END` resolves the alert that the matching start event opened. - A health check notification whose new status is **Healthy** resolves the open alert for that health check. - One-off notices, such as certificate expiry or usage notifications, have no end event and stay open until resolved in EvoHub. - `PENDING` events (Workers observability) are ignored. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/ibm-instana.md # IBM Instana EvoHub receives Instana issues and incidents through a **Webhook** alert channel. An open issue opens an EvoHub alert, and Instana closing the issue resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **IBM Instana**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add the alert channel In Instana, open the alert channels settings (**Settings → Events & alerts → Alert channels** — under **Global settings** in recent versions), click **Add alert channel**, choose the generic **Webhook** type and add your webhook URL. ### Test it Click **Test channel**. EvoHub answers with success and opens no alert. ### Use it in alert configurations Add the channel to the alert configurations (application, infrastructure, website or global alerts) that should page. ::: Instana's own issue JSON is read as it is. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `issue.text`, plus " on `entityLabel`" when it is not already in the text. | | Description | `issue.suggestion`. | | Labels | `zone`, `fqdn`, `entity`, `entity_label`, `entity_type`, `container`, `service`, `tags`, `issue_type`, and `url` (the issue link). | | Fingerprint | `issue.id`. | ### Severity | Instana severity | EvoHub severity | | --- | --- | | 10 (critical) | critical | | 5 (warning) | medium | | no severity, on an incident | critical | | anything else | medium | ## Resolve and deduplication - `state: CLOSED` resolves the alert for that issue ID. - Updates to an open issue are recorded as **Retriggered** on its alert. - **Presence** events (`type: presence`): `offline` opens * on is offline* as **high**, and `online` for the same entity resolves it. The two events carry different IDs, so the alert is identified by the host, entity type, entity label and container. - **Change** events (`type: change`) are answered `200` and open nothing: they have no state and never close. You can also connect Instana through a **Prometheus Alertmanager** alert channel pointed at a **Prometheus** integration; see [Prometheus](https://docs.evohub.io/prometheus-alertmanager.md). The Webhook channel described here carries more detail. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Prometheus](https://docs.evohub.io/prometheus-alertmanager.md) --- Source: https://docs.evohub.io/netdata.md # Netdata EvoHub receives Netdata Cloud notifications through the **Webhook** integration. Warning and critical alerts open EvoHub alerts and clear when Netdata sends them as cleared. Node reachability notifications open an alert when a node is unreachable and resolve it when the node is back. Netdata's webhook integration needs a paid Netdata Cloud space. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Netdata**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add the webhook in Netdata In Netdata Cloud, open **Space settings → Alerts & Notifications → + Add configuration → Webhook**. Set **Webhook URL** to your webhook URL. Keep the default authentication mechanism; EvoHub needs no Basic or Bearer credentials — the key in the URL authenticates the call. Choose the rooms and notification types. ### Send a test Click **Test**. In EvoHub, an **info** alert called "Netdata test notification received" appears. It pages nobody, is not posted to Slack and does not count toward usage; its description holds the verification token. ### Paste the token Copy the token into the **Verification Token** field in Netdata and save the configuration. Resolve the test alert. ::: ## What EvoHub reads ### Alert notifications | EvoHub alert | Taken from | | --- | --- | | Title | "`alert` on `chart`", for example "disk_space_usage on disk_space./". | | Description | `message`, followed by `info`. | | Labels | `chart`, `context`, `family`, `class`, `space`, `rooms`, `host` (the node, when EvoHub finds one — see below), and `url` (the `alert_url`). | | Fingerprint | The alert name, chart and node. | | Netdata severity | EvoHub | | --- | --- | | critical | critical | | warning | medium | | clear | resolves the alert | ### Reachability notifications A node becoming unreachable opens "`host` is unreachable" as **critical**. A notification with `status.reachable: true` resolves it. ## Resolve and deduplication - An alert is identified by the alert name, the chart and the node, so the same alert on two nodes is two EvoHub alerts. Netdata's alert notification has no documented node field; EvoHub takes the node from `host` or `node` when present, else from the node in `alert_url`, else from the node the message names ("… on node db-01 …"). With none of these, the alert name and chart identify it. - `severity: clear` resolves the open alert with the same alert name, chart and node. - A warning that becomes critical, or repeats while open, is recorded as **Retriggered** on the same alert. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/aws-eventbridge.md # AWS EventBridge (GuardDuty, Security Hub, AWS Health) EvoHub receives AWS events from **Amazon EventBridge**, either directly through an **API destination** or through an **SNS topic**. GuardDuty findings, Security Hub findings, AWS Health events and CloudWatch alarm state changes are read field by field; any other event you route opens one alert per event. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **AWS EventBridge**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**; the integration key is the value after `key=`. ### Create the API destination In the EventBridge console, go to **API destinations → Create API destination**. Set **API destination endpoint** to your webhook URL and **HTTP method** to `POST`. Create a new **connection** with **Authorization type** *API Key*, **API key name** `X-EvoHub-Key` and the integration key as **Value**. ### Create a rule Go to **Rules → Create rule**, choose an event pattern (see below) and select the API destination as the target. Keep the target input as **Matched events** — do not add an input transformer; EvoHub needs the event as AWS sends it. ::: ### Or use an SNS topic Make an SNS topic the rule's target and add a subscription with **Protocol** HTTPS and **Endpoint** your webhook URL. EvoHub verifies the SNS signature on every SNS message and confirms the subscription by itself; check in SNS that it shows an ARN rather than *PendingConfirmation*. Raw message delivery can be on or off; with it on, SNS sends the bare event without a signature, and the key in the URL is what authenticates it. ### Event patterns GuardDuty findings of high severity and above: ```json {"source": ["aws.guardduty"], "detail-type": ["GuardDuty Finding"], "detail": {"severity": [{"numeric": [">=", 7]}]}} ``` Security Hub findings — pick the format your Security Hub sends. Both patterns open alerts only for high and critical findings, and also let through the updates that close a finding. Keep that second part: when a control check passes, Security Hub updates the finding to `PASSED` *and* lowers its severity to informational, so a pattern that filters on severity alone drops the very update that resolves the alert. Security Hub CSPM (ASFF): ```json {"source": ["aws.securityhub"], "detail-type": ["Security Hub Findings - Imported"], "detail": {"findings": {"$or": [{"Severity": {"Label": ["HIGH", "CRITICAL"]}}, {"Compliance": {"Status": ["PASSED"]}}, {"RecordState": ["ARCHIVED"]}, {"Workflow": {"Status": ["RESOLVED", "SUPPRESSED"]}}]}}} ``` Security Hub (OCSF findings): ```json {"source": ["aws.securityhub"], "detail-type": ["Findings Imported V2"], "detail": {"findings": {"$or": [{"severity": ["High", "Critical"]}, {"status": ["Suppressed", "Resolved", "Archived"]}, {"compliance": {"status": ["Pass"]}}]}}} ``` AWS Health events: ```json {"source": ["aws.health"], "detail-type": ["AWS Health Event"]} ``` :::warning GuardDuty findings are also imported into Security Hub. Route one of the two to EvoHub, not both, or every finding pages twice. ::: ## What EvoHub reads | Event | Title | Severity | Resolves when | | --- | --- | --- | --- | | GuardDuty finding | The finding title. | 9.0–10 critical, 7.0–8.9 high, 4.0–6.9 medium, below 4 low. | GuardDuty sends no recovery — resolve in EvoHub. An archived finding, if sent, resolves. | | Security Hub finding (ASFF) | `Title`. | `Severity.Label`: critical, high, medium, low, informational. | The record is archived, the workflow status is resolved or suppressed, or the control check passes. | | Security Hub finding (OCSF) | `finding_info.title`. | `severity` (or `severity_id`). | The status is suppressed, resolved or archived, the finding is closed, or the compliance check passes. | | AWS Health event | Service and event type code. | Issue high, investigation medium, scheduled change low, account notification info. | `statusCode` is closed. | | CloudWatch alarm state change (`CloudWatch Alarm State Change` or `CloudWatch Alarm Contributor State Change`) | The alarm name (plus the contributor's attributes). | `ALARM` critical, `INSUFFICIENT_DATA` low. A composite alarm whose actions are suppressed (for example by a maintenance alarm) opens nothing. | The state is `OK`. Keyed by alarm ARN (and contributor ID). The alert links to the alarm in the CloudWatch console. | | Any other event | The detail type and first resource. | Medium. | Never — resolve in EvoHub. | The description is the finding description with its remediation text, the Health event description, or the event's `detail` for any other event. Every alert is labelled with the AWS `source`, `detail_type`, `account` and `region`; findings also carry the resource, finding type, product and a console or remediation link. ## Deduplication - A GuardDuty finding that recurs is sent again with the same ID and is recorded as **Retriggered** on the open alert. - Security Hub findings are keyed by product and finding ID; each finding in an event is its own alert. - AWS Health events are keyed by event ARN and affected account. Backup copies that AWS delivers in a second Region carry the same key, so a rule that receives both copies sees one alert (the second copy is recorded as **Retriggered**), and a rule set up only in the backup Region still gets every event. ## Related - [AWS CloudTrail](https://docs.evohub.io/aws-cloudtrail.md) - [Integrations overview](https://docs.evohub.io/integrations-overview.md) --- Source: https://docs.evohub.io/microsoft-teams.md # Microsoft Teams EvoHub can post On-Call alerts to a Microsoft Teams channel. Each alert arrives as a card with its title, severity, source and start time, and buttons to acknowledge it, resolve it or open it in EvoHub. Teams runs alongside paging: it never replaces the phone calls, push notifications and emails your escalation policies send. Teams messages are free: they are not counted towards your usage. :::note Microsoft retired Office 365 connectors in Teams in May 2026. Old "Incoming Webhook" URLs (`outlook.office.com/webhook/…`) no longer work and EvoHub does not accept them. Use a Workflows webhook as described below. ::: ## Add a Teams channel :::steps ### Create the Workflows webhook in Teams In Teams, open the channel, click **•••** next to its name and choose **Workflows**. Pick the template **Send webhook alerts to a channel**, confirm the team and channel, and click **Add workflow**. Copy the URL Teams shows you. ### Add the channel in EvoHub In EvoHub, click your avatar and go to **Organization → Chat apps**. On the **Microsoft Teams** card click **Add channel**, give it a **Name**, paste the **Workflows webhook URL**, and choose whether to **Post every new On-Call alert here**. ### Check it works Click **Add and send a test**. A card saying "EvoHub is connected" appears in the channel. You can send another test at any time with **Send test**. ::: The URL is a credential: anyone who has it can post to your channel. EvoHub stores it encrypted and never shows it again. To change it, remove the channel and add it again with the new URL. Only URLs on `powerplatform.com` or `logic.azure.com` over `https` are accepted. Adding, testing and removing chat channels needs the On-Call settings permission and the Organization settings permission. A Teams channel added here can also be picked as an Uptime notification channel and a changelog notification destination. ## Where alerts are posted - **Every new alert** — channels marked **Every new On-Call alert** in **Chat apps** get every new alert. - **From an escalation step** — open the escalation policy, click **Edit**, and in the step's **Chat** area pick the channel under **Chat channel**. The step still pages its target; the post is in addition. See [Escalation policies](https://docs.evohub.io/escalation-policies.md). An alert is posted to a channel once, even if both apply. ## Acknowledge and resolve from Teams The **Acknowledge** and **Resolve** buttons open EvoHub in your browser. You confirm the action there, signed in as yourself: - You need permission to respond to alerts. If the alert belongs to a team, you must be in that team, be an admin, or be the person the alert is assigned to. - If you are signed in to a different organization, EvoHub tells you to switch to the alert's organization first. - Each button link works once and expires after 24 hours. After that, open the alert from **Open in EvoHub** instead. The action is recorded on the alert's timeline under your name, for example "Acknowledged from Microsoft Teams." ## When the alert changes A Workflows webhook cannot edit a card it has posted. So when the alert is acknowledged, taken over or resolved — anywhere: in EvoHub, the mobile app, by phone or in Slack — EvoHub posts a short follow-up card under the original, for example "Resolved by Ada Lovelace: Disk full on db-1". The original card keeps its buttons; using one on an alert that is already done just tells you so. Cards are posted by Teams' "Flow bot". Teams limits a channel to a few messages per second; EvoHub waits and retries once when Teams asks it to slow down. ## Remove a channel In **Organization → Chat apps**, click the remove (trash) button next to the channel and confirm with **Remove**. Escalation steps that named it post nowhere until you choose another channel. Delete the workflow in Teams as well if you no longer need it. ## Related - [Slack](https://docs.evohub.io/slack.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/aws-cloudtrail.md # AWS CloudTrail CloudTrail needs no integration of its own. Send the trail to CloudWatch Logs, turn the events you care about into a CloudWatch alarm with a **metric filter**, and send the alarm to EvoHub through the [AWS CloudWatch](https://docs.evohub.io/aws-cloudwatch.md) integration. The alert resolves when the alarm returns to OK. ## Set it up :::steps ### Create a CloudWatch integration In EvoHub, add an **AWS CloudWatch** integration and subscribe its webhook URL to an SNS topic (HTTPS, raw message delivery off), as described in [AWS CloudWatch](https://docs.evohub.io/aws-cloudwatch.md). ### Send the trail to CloudWatch Logs In the CloudTrail console, edit the trail and turn on **CloudWatch Logs**, choosing a log group. ### Create a metric filter In CloudWatch, open the log group, choose **Create metric filter** and paste a pattern (examples below). Give the metric a name and a value of `1`. ### Create the alarm Create an alarm on the metric: statistic **Sum**, period 5 minutes, threshold **greater than or equal to 1**, and **treat missing data as not breaching**. Set the SNS topic as both the **In alarm** and the **OK** action. ::: ## Example filters Root user sign-in or API use: ```text { $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" } ``` Logging stopped or the trail changed: ```text { ($.eventName = StopLogging) || ($.eventName = DeleteTrail) || ($.eventName = UpdateTrail) } ``` The alert title is the alarm name, so name each alarm after what it watches. ## Related - [AWS EventBridge](https://docs.evohub.io/aws-eventbridge.md) - [Integrations overview](https://docs.evohub.io/integrations-overview.md) --- Source: https://docs.evohub.io/elastic-kibana.md # Elastic / Kibana EvoHub receives Kibana alerts through a **Webhook connector** used by your alerting rules. Each rule gets two actions with the same body: one when the alert is active, one when it recovers. The recovered action resolves the EvoHub alert. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Elastic / Kibana**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create the connector In Kibana, go to **Stack Management → Connectors → Create connector → Webhook**. Set **Method** to `POST`, **URL** to your webhook URL and add the header `Content-Type: application/json`. ### Add two actions to each rule Edit the rule and add the connector under **Actions** twice: once with **Run when** set to the rule's active (firing) group, once with **Run when** *Recovered*. Set the action frequency to **On status changes** for each alert, not a summary of alerts. Use the body below for both. ::: ```json {"rule_id":"{{rule.id}}","rule_name":"{{rule.name}}","rule_url":"{{rule.url}}","alert_id":"{{alert.id}}","action_group":"{{alert.actionGroup}}","reason":"{{context.reason}}","severity":"{{context.rule.severity}}","tags":"{{rule.tags}}"} ``` The connector's **Test** tab sends the body with the variables unfilled. EvoHub answers with success and opens no alert. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `rule_name`, followed by `alert_id` in brackets (the host or group the alert is for). | | Description | `reason`. | | Severity | `severity` when the rule has one (security detection rules); otherwise **high**. | | Labels | `rule_id`, `alert_id`, `action_group`, `tags`, and `url` (the rule's link). | ## Resolve and deduplication - An alert is identified by the rule and `alert_id`, so each host or group of a rule is its own EvoHub alert, and repeats while it is open are recorded as **Retriggered**. - The **Recovered** action sends `action_group` `recovered`, which resolves the matching alert. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Sumo Logic](https://docs.evohub.io/sumo-logic.md) --- Source: https://docs.evohub.io/manageengine-opmanager.md # ManageEngine OpManager EvoHub receives OpManager alarms through a notification profile of type **Invoke a Webhook**. Each alarm opens an EvoHub alert; when OpManager sends the same alarm as **Clear**, the alert resolves. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **ManageEngine OpManager**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create the notification profile In OpManager, go to **Settings → Notifications → Notification Profiles → Add** and choose **Invoke a Webhook**. Choose the method `POST`, paste your webhook URL, set the data type to **raw** and the payload type to **JSON**, and paste the body below. ### Choose the criteria Select the alarm severities that should page and also the **clear** criterion. Without it OpManager never tells EvoHub the alarm has cleared. ### Associate devices Apply the profile to the devices or groups to monitor. ::: ```json {"alarm_id":"$alarmid","entity":"$entity","device":"$displayName","category":"$category","severity":"$stringseverity","message":"$message","event_type":"$eventType","ip":"$DeviceField(ipAddress)","time":"$strModTime"} ``` **Test Action** sends the variables without values. EvoHub answers with success and opens no alert. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `device` and `event_type`, for example *core-sw-01 – CPU Utilization*. | | Description | `message`. | | Labels | `category`, `ip`, `device`, `alarm_id`, `entity`, `time`, `severity`. | ### Severity | OpManager severity | EvoHub severity | | --- | --- | | Critical, Service Down | critical | | Trouble | high | | Attention | medium | | Clear | resolves the alert | ## Resolve and deduplication An alarm is identified by its `entity` (the device and monitor). A repeat while the alert is open is recorded as **Retriggered**; a **Clear** for the same entity resolves it. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [SolarWinds Orion](https://docs.evohub.io/solarwinds.md) --- Source: https://docs.evohub.io/solarwinds.md # SolarWinds Orion EvoHub receives SolarWinds Orion (SolarWinds Platform) alerts through the **Send a GET or POST Request to a Web Server** action. The same body goes on the alert's trigger action and its reset action; the reset resolves the EvoHub alert. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **SolarWinds Orion**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add the trigger action In Orion, go to **Alerts → Manage Alerts**, edit the alert and open **Trigger Actions → Add Action → Send a GET or POST Request to a Web Server**. Set the URL to your webhook URL, choose **POST**, content type `application/json`, and paste the body below. ### Add the reset action Open **Reset Actions** and add the same action with the same body, changing `"state":"trigger"` to `"state":"reset"`. ::: ```json {"state":"trigger","alert_id":"${N=Alerting;M=AlertID}","name":"${N=Alerting;M=AlertName}","message":"${N=Alerting;M=AlertMessage}","severity":"${N=Alerting;M=Severity}","object":"${N=SwisEntity;M=Caption}","url":"${N=Alerting;M=AlertDetailsURL}"} ``` A body whose variables are not filled in is answered with success and opens no alert. Orion inserts values without escaping them; EvoHub repairs line breaks, quotes and backslashes (a path such as `D:\Data`) that end up inside values. A body that still cannot be read is not refused — see [Integrations overview](https://docs.evohub.io/integrations-overview.md#bodies-that-cannot-be-read). ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `name` on `object`, for example *Node is down on edge-rtr-03*. | | Description | `message`. | | Labels | `alert_id`, `object`, `url`. | ### Severity | Orion severity | EvoHub severity | | --- | --- | | Critical | critical | | Serious | high | | Warning | medium | | Notice | low | | Informational | info | ## Resolve and deduplication An alert is identified by the alert definition and the object it fired on, so each node or interface is its own EvoHub alert. `"state":"reset"` resolves it. SolarWinds Observability (SaaS) uses its own payload variables, not Orion's. A fully custom webhook payload with the same keys is read the same way; without a `"state":"reset"` delivery, those alerts are resolved in EvoHub. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [ManageEngine OpManager](https://docs.evohub.io/manageengine-opmanager.md) --- Source: https://docs.evohub.io/sumo-logic.md # Sumo Logic EvoHub receives Sumo Logic monitor notifications through a **webhook connection**. The same body is used for the alert and the recovery payload; the recovery resolves the EvoHub alert. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Sumo Logic**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create the connection In Sumo Logic, go to **Monitoring → Connections** (Classic UI: **Manage Data → Monitoring → Connections**), add a connection and choose **Webhook**. Set **URL** to your webhook URL and paste the body below as both the **Alert Payload** and the **Recovery Payload**. ### Use it in monitors In each monitor, under **Notifications**, add the connection, select the trigger types to send and turn on **Recovery**. ::: ```json {"id":"{{Id}}","name":"{{Name}}","description":"{{Description}}","trigger_type":"{{TriggerType}}","trigger_value":"{{TriggerValue}}","trigger_condition":"{{TriggerCondition}}","num_results":"{{NumQueryResults}}","query_url":"{{QueryURL}}","alert_url":"{{AlertResponseUrl}}"} ``` Leave a bare `{{ResultsJson}}` out of the body: Sumo Logic inserts it as raw JSON, and the empty result of a recovery would make the body unreadable. ### Monitors with alert grouping A monitor with **alert grouping** sends one notification per group (per host, say). With the body above all groups share one EvoHub alert, because the alert is identified by the monitor. To get one alert per group, add the grouping field to the body before the closing `}`: ```json ,"group":"{{ResultsJson._sourceHost}}" ``` The alert is then identified by the monitor and the group, and a recovery resolves only its own group's alert. That needs the field in the **Recovery Payload** too: if it comes through empty there, the recovery cannot find the group's alert — leave `group` out and keep one alert per monitor. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `name` (the monitor name). | | Description | `description`. | | Labels | `monitor_id`, `group`, `trigger_type`, `trigger_value`, `trigger_condition`, `num_results`, `query_url`, and `url` (the alert response page). | ### Severity | Trigger type | EvoHub severity | | --- | --- | | Critical | critical | | Warning | medium | | Missing Data | low | | Normal | high | | ResolvedCritical, ResolvedWarning, ResolvedMissingData | resolves the alert | ## Resolve and deduplication An alert is identified by the monitor, or by the monitor and the group when the body has one. A repeat while it is open is recorded as **Retriggered**, and the recovery payload resolves it. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Elastic / Kibana](https://docs.evohub.io/elastic-kibana.md) --- Source: https://docs.evohub.io/appdynamics.md # AppDynamics EvoHub receives AppDynamics health rule violations through an **HTTP Request Template** used by a policy action. A violation that starts opens an EvoHub alert; one that ends or is canceled resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **AppDynamics**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create the template In AppDynamics, go to **Alert & Respond → HTTP Request Templates → New**. Set the method to `POST`, the **Raw URL** to your webhook URL, the payload MIME type to `application/json`, and paste the payload below. Use the template's **Test** button to check the variables resolve. ### Create the action and policy Create an **HTTP Request** action that uses the template. In **Policies**, trigger it on health rule violations started, continuing, upgraded, downgraded, ended and canceled. ::: ```json {"policy":"${policy.name}","event_type":"${latestEvent.eventType}","incident_id":"${latestEvent.incident.id}","health_rule":"${latestEvent.healthRule.name}","summary":"${latestEvent.summaryMessage}","display_name":"${latestEvent.displayName}","severity":"${latestEvent.severity}","app":"${latestEvent.application.name}","tier":"${latestEvent.tier.name}","node":"${latestEvent.node.name}","link":"${latestEvent.deepLink}"} ``` ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | The health rule (else `policy`) and `app`, for example *Business Transaction response time is much higher than normal — shop-prod*. | | Description | `summary` with its HTML removed. | | Labels | `event_type`, `app`, `tier`, `node`, `policy`, `health_rule`, `incident_id`, and `url` (the deep link). | ### Severity | Event type | EvoHub severity | | --- | --- | | `POLICY_OPEN_CRITICAL`, `POLICY_CONTINUES_CRITICAL`, `POLICY_UPGRADED`, `ANOMALY_OPEN_CRITICAL`, `ANOMALY_UPGRADED` | critical | | `POLICY_OPEN_WARNING`, `POLICY_CONTINUES_WARNING`, `POLICY_DOWNGRADED`, `ANOMALY_OPEN_WARNING`, `ANOMALY_DOWNGRADED` | medium | | `POLICY_CLOSE_*`, `POLICY_CANCELED_*`, `ANOMALY_CLOSE_*`, `ANOMALY_CANCELLED_*` | resolves the alert | | Other events | `severity`: ERROR high, WARN medium, INFO info | ## Resolve and deduplication An alert is identified by the health rule violation (`incident_id`), so two business transactions or two nodes violating one policy are two alerts. Without an incident ID — an older body, or an event that is not a health rule violation — it is identified by the policy, the application and the node (or the tier when there is no node). Continuing, upgraded and downgraded violations update the open alert; a closed or canceled violation resolves it. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Honeycomb](https://docs.evohub.io/honeycomb.md) --- Source: https://docs.evohub.io/honeycomb.md # Honeycomb EvoHub receives Honeycomb trigger notifications through a **webhook integration**. Honeycomb's default payload is read as it is: a trigger that fires opens an EvoHub alert, and the same trigger returning to OK resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Honeycomb**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create the webhook In Honeycomb, go to **Team settings → Integrations → Add integration → Webhook**. Set **Webhook URL** to your webhook URL and leave **Shared Secret** empty — the key in the URL authenticates the call. If you prefer the key out of the URL, remove `?key=…` from it and paste the key as the **Shared Secret** instead. ### Test it A test notification from Honeycomb is answered with success and opens no alert. ### Add it to triggers In each trigger, add the webhook under **Recipients**. ::: ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | The trigger `name`. | | Description | `description`, followed by the trigger's own description. | | Severity | **high**. | | Labels | `trigger_id`, `operator`, `threshold`, `trigger_url`, `groups` (the groups that crossed the threshold) and `url` (the query result). | ## Resolve and deduplication An alert is identified by the trigger. Repeat firings while it is open are recorded as **Retriggered**; `status` `OK` resolves it. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [AppDynamics](https://docs.evohub.io/appdynamics.md) --- Source: https://docs.evohub.io/wazuh.md # Wazuh Wazuh has no webhook of its own: its integrator daemon runs a script for every alert at or above a level you choose. EvoHub provides that script. It posts the alert exactly as Wazuh wrote it, and uses only Python's standard library and the Python that ships with the Wazuh manager. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Wazuh**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Install the script On the Wazuh manager, save the script below as `/var/ossec/integrations/custom-evohub`, then make it executable by the `wazuh` group: ```bash chmod 750 /var/ossec/integrations/custom-evohub chown root:wazuh /var/ossec/integrations/custom-evohub ``` ### Test the connection Run the script once by hand. EvoHub answers with success and opens no alert: ```bash /var/ossec/integrations/custom-evohub --test 'https://evohub.io/ingest/wazuh?key=YOUR_INTEGRATION_KEY' ``` ### Turn on the integration Add the block below to `/var/ossec/etc/ossec.conf`, inside ``, and restart the manager with `systemctl restart wazuh-manager`. ::: Script (`/var/ossec/integrations/custom-evohub`): ```python #!/var/ossec/framework/python/bin/python3 # EvoHub On-Call — Wazuh custom integration. # Install as /var/ossec/integrations/custom-evohub (root:wazuh, mode 750). # integratord runs it as: custom-evohub # Test by hand: custom-evohub --test import json import sys import urllib.error import urllib.request def post(url, payload): req = urllib.request.Request( url, data=json.dumps(payload).encode("utf-8"), method="POST", headers={"Content-Type": "application/json", "User-Agent": "evohub-wazuh/1"}, ) try: with urllib.request.urlopen(req, timeout=10) as resp: resp.read() return 0 except urllib.error.HTTPError as e: sys.stderr.write("evohub: EvoHub answered HTTP %d\n" % e.code) except (urllib.error.URLError, OSError) as e: sys.stderr.write("evohub: could not reach EvoHub: %s\n" % getattr(e, "reason", e)) return 1 def main(argv): if len(argv) == 3 and argv[1] == "--test": return post(argv[2], {"evohub_test": True}) if len(argv) < 4: sys.stderr.write("usage: custom-evohub \n") return 2 with open(argv[1], encoding="utf-8") as f: alert = json.load(f) return post(argv[3], alert) if __name__ == "__main__": sys.exit(main(sys.argv)) ``` `ossec.conf`: ```xml custom-evohub https://evohub.io/ingest/wazuh?key=YOUR_INTEGRATION_KEY 10 json ``` Every alert at `level` 10 or higher is sent. Add `` or `` to the block to send only some rules, or lower the level to send more. If the script cannot reach EvoHub it prints the reason — never the URL — and exits with an error. > [!NOTE] > The first line of the script points at the Python bundled with Wazuh. If your manager has its own Python 3, `#!/usr/bin/env python3` works too. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `rule.description` and the agent name, for example *sshd: brute force trying to get access to the system. Authentication failed. – web-01*. | | Description | `full_log`, followed by the log location. | | Labels | `rule_id`, `rule_level`, `groups`, `mitre_id`, `mitre_tactic`, `agent_id`, `agent_name`, `agent_ip`, `manager`, `decoder`, `location`, `alert_id`, `timestamp` and, when the decoder found them, `srcip`, `srcport`, `srcuser`, `dstuser`, `dstip`. | ### Severity | Wazuh rule level | EvoHub severity | | --- | --- | | 12 and above | critical | | 10–11 | high | | 7–9 | medium | | 4–6 | low | | below 4 | info | ## Resolve and deduplication An alert is identified by the rule and the agent. While it is open, the same rule firing again on the same agent is recorded as **Retriggered** instead of paging again; the same rule on another agent is a separate alert. Wazuh alerts are events and Wazuh sends no recovery, so alerts stay open until someone resolves them in EvoHub. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [FortiGate](https://docs.evohub.io/fortigate.md) --- Source: https://docs.evohub.io/checkmk.md # Checkmk Checkmk notifies through scripts. EvoHub provides one: a problem opens an EvoHub alert and the recovery resolves it. The script uses only Python's standard library and sends a fixed set of fields — never the URL, the rule parameters or contact details. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Checkmk**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Install the script As the site user (`omd su `), save the script below as `~/local/share/check_mk/notifications/evohub` and make it executable with `chmod +x`. Its second line, `# EvoHub On-Call`, is the name it gets in the notification method list. ### Test the connection Run it once by hand. EvoHub answers with success and opens no alert: ```bash ~/local/share/check_mk/notifications/evohub --test 'https://evohub.io/ingest/checkmk?key=YOUR_INTEGRATION_KEY' ``` ### Create the notification rule Go to **Setup → Events → Notifications → Add notification rule** (Checkmk 2.3 and 2.4; on 2.2 and earlier, **Setup → Notifications**). Choose the notification method **EvoHub On-Call** and enter your webhook URL as the first parameter. For contacts, select one user (for example a dedicated `evohub` user): the script runs once per selected contact. Include the changes back to **OK** and **UP** in the triggering events — the recovery is what resolves the alert. ### Activate changes Activate the pending changes. ::: Script (`~/local/share/check_mk/notifications/evohub`): ```python #!/usr/bin/env python3 # EvoHub On-Call # Checkmk notification script. Install as the site user: # ~/local/share/check_mk/notifications/evohub (chmod +x) # Rule parameter 1 = your EvoHub integration URL. # Test by hand (as the site user): ./evohub --test '' import json import os import sys import urllib.error import urllib.request # Only these variables are sent: never the rule parameters (they hold the # URL and its key) nor contact details. FIELDS = ( "WHAT", "NOTIFICATIONTYPE", "HOSTNAME", "HOSTALIAS", "HOSTADDRESS", "HOSTSTATE", "HOSTSTATEID", "HOSTOUTPUT", "LONGHOSTOUTPUT", "SERVICEDESC", "SERVICESTATE", "SERVICESTATEID", "SERVICEOUTPUT", "LONGSERVICEOUTPUT", "HOSTTAGS", "OMD_SITE", "SHORTDATETIME", "HOSTURL", "SERVICEURL", "NOTIFICATIONAUTHOR", "NOTIFICATIONCOMMENT", ) def post(url, payload): req = urllib.request.Request( url, data=json.dumps(payload).encode("utf-8"), method="POST", headers={"Content-Type": "application/json", "User-Agent": "evohub-checkmk/1"}, ) try: with urllib.request.urlopen(req, timeout=10) as resp: resp.read() print("EvoHub: delivered") return 0 except urllib.error.HTTPError as e: print("EvoHub answered HTTP %d" % e.code) # 429 and 5xx are worth a retry (exit 1); other refusals are final. return 1 if e.code == 429 or e.code >= 500 else 2 except (urllib.error.URLError, OSError) as e: print("Could not reach EvoHub: %s" % getattr(e, "reason", e)) return 1 def main(argv): if len(argv) == 3 and argv[1] == "--test": return post(argv[2], {"evohub_test": True}) url = os.environ.get("NOTIFY_PARAMETER_1", "").strip() if not url: print("Set the EvoHub integration URL as the rule's first parameter") return 2 payload = {} for name in FIELDS: value = os.environ.get("NOTIFY_" + name) if value: payload["NOTIFY_" + name] = value return post(url, payload) if __name__ == "__main__": sys.exit(main(sys.argv)) ``` The script exits with `0` when EvoHub accepted the notification, `1` when it is worth retrying (network error, `429` or `5xx`) and `2` when retrying will not help. Its output appears in Checkmk's notification log. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | Host, service and state, for example *web01 / HTTP shop is CRIT*, or *web01 is DOWN* for a host. | | Description | The check output, followed by the long output. | | Labels | `host`, `service`, `state`, `address`, `site`, `url`, `host_tags`, `time`. | ### Severity The numeric state is used, so translated or abbreviated state names do not matter. | Checkmk state | EvoHub severity | | --- | --- | | CRIT (service), DOWN (host) | critical | | UNREACH (host) | high | | WARN | medium | | UNKNOWN | low | ## Resolve and deduplication An alert is identified by the host, or the host and service. A **PROBLEM** notification opens it (a repeat while it is open is recorded as **Retriggered**); a **RECOVERY** notification resolves it. Acknowledgement, downtime, flapping and custom notifications are accepted and change nothing. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Icinga](https://docs.evohub.io/icinga.md) --- Source: https://docs.evohub.io/icinga.md # Icinga EvoHub reads Icinga in two ways, on the same URL: - **Icinga Notifications** (the notification module of Icinga DB Web) has a **Webhook** channel. Its default request body is read as it is. - **Icinga 2** without Icinga Notifications runs a notification command. EvoHub provides the script and the configuration. In both, a problem opens an EvoHub alert and the recovery resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Icinga**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Connect Icinga Follow one of the two tabs below. ::: :::tabs ::tab{title="Icinga Notifications"} 1. In Icinga Web, open the Notifications module's configuration (**Configuration → Modules → notifications**), go to the **Channels** tab, click **Add Channel** and choose the type **Webhook**. 2. Set **Method** to `POST`, **URL Template** to your webhook URL, keep **Request Body Template** at its default `{{json .}}`, and set **Response Status Codes** to `200`. 3. Add the channel to a contact and use that contact in your event rules. ::tab{title="Icinga 2 script"} 1. Save the script below as `/etc/icinga2/scripts/evohub-notification` and make it executable with `chmod 755`. 2. Test the connection. EvoHub answers with success and opens no alert: `EVOHUB_URL='https://evohub.io/ingest/icinga?key=YOUR_INTEGRATION_KEY' /etc/icinga2/scripts/evohub-notification --test` 3. Save the configuration below as `/etc/icinga2/conf.d/evohub.conf`, with your webhook URL in both `EVOHUB_URL` lines. 4. Check and reload: `icinga2 daemon -C && systemctl reload icinga2`. ::: Script (`/etc/icinga2/scripts/evohub-notification`, Python 3 standard library only): ```python #!/usr/bin/env python3 # EvoHub On-Call — Icinga 2 notification script. # Install as /etc/icinga2/scripts/evohub-notification (chmod 755). # Reads the EVOHUB_* environment the NotificationCommand sets. # Test by hand: EVOHUB_URL='' ./evohub-notification --test import json import os import sys import urllib.error import urllib.request FIELDS = { "notification_type": "EVOHUB_NOTIFICATION_TYPE", "host": "EVOHUB_HOST", "host_display_name": "EVOHUB_HOST_DISPLAY_NAME", "service": "EVOHUB_SERVICE", "service_display_name": "EVOHUB_SERVICE_DISPLAY_NAME", "state": "EVOHUB_STATE", "output": "EVOHUB_OUTPUT", "address": "EVOHUB_ADDRESS", "time": "EVOHUB_TIME", "author": "EVOHUB_AUTHOR", "comment": "EVOHUB_COMMENT", } def main(argv): url = os.environ.get("EVOHUB_URL", "").strip() if not url: sys.stderr.write("evohub: EVOHUB_URL is not set\n") return 2 if len(argv) == 2 and argv[1] == "--test": payload = {"evohub_test": True} else: payload = {k: os.environ[v] for k, v in FIELDS.items() if os.environ.get(v)} req = urllib.request.Request( url, data=json.dumps(payload).encode("utf-8"), method="POST", headers={"Content-Type": "application/json", "User-Agent": "evohub-icinga2/1"}, ) try: with urllib.request.urlopen(req, timeout=10) as resp: resp.read() return 0 except urllib.error.HTTPError as e: sys.stderr.write("evohub: EvoHub answered HTTP %d\n" % e.code) except (urllib.error.URLError, OSError) as e: sys.stderr.write("evohub: could not reach EvoHub: %s\n" % getattr(e, "reason", e)) return 1 if __name__ == "__main__": sys.exit(main(sys.argv)) ``` Configuration (`/etc/icinga2/conf.d/evohub.conf`): ```text object User "evohub" { display_name = "EvoHub On-Call" } object NotificationCommand "evohub-host-notification" { command = [ ConfigDir + "/scripts/evohub-notification" ] env = { EVOHUB_URL = "https://evohub.io/ingest/icinga?key=YOUR_INTEGRATION_KEY" EVOHUB_NOTIFICATION_TYPE = "$notification.type$" EVOHUB_HOST = "$host.name$" EVOHUB_HOST_DISPLAY_NAME = "$host.display_name$" EVOHUB_STATE = "$host.state$" EVOHUB_OUTPUT = "$host.output$" EVOHUB_ADDRESS = "$host.address$" EVOHUB_TIME = "$icinga.long_date_time$" } } object NotificationCommand "evohub-service-notification" { command = [ ConfigDir + "/scripts/evohub-notification" ] env = { EVOHUB_URL = "https://evohub.io/ingest/icinga?key=YOUR_INTEGRATION_KEY" EVOHUB_NOTIFICATION_TYPE = "$notification.type$" EVOHUB_HOST = "$host.name$" EVOHUB_HOST_DISPLAY_NAME = "$host.display_name$" EVOHUB_SERVICE = "$service.name$" EVOHUB_SERVICE_DISPLAY_NAME = "$service.display_name$" EVOHUB_STATE = "$service.state$" EVOHUB_OUTPUT = "$service.output$" EVOHUB_ADDRESS = "$host.address$" EVOHUB_TIME = "$icinga.long_date_time$" } } apply Notification "evohub" to Host { command = "evohub-host-notification" users = [ "evohub" ] types = [ Problem, Recovery ] states = [ Up, Down ] interval = 0 assign where true } apply Notification "evohub" to Service { command = "evohub-service-notification" users = [ "evohub" ] types = [ Problem, Recovery ] states = [ OK, Warning, Critical, Unknown ] interval = 0 assign where true } ``` The configuration notifies a single `evohub` user, once per problem (`interval = 0`) and once on recovery, for every host and service. Narrow `assign where` to send only some of them. Acknowledgement, downtime and flapping notifications, if you add those types, change nothing in EvoHub. ## What EvoHub reads | EvoHub alert | Icinga Notifications | Icinga 2 script | | --- | --- | --- | | Title | The object name. | Host, service and state, for example *db-02 / PostgreSQL connections is CRITICAL*. | | Description | The event message. | The check output. | | Labels | The object's tags (such as `host` and `service`), `severity`, `url`, `incident_id`. | `host`, `service`, `state`, `address`, `time`. | ### Severity | Icinga Notifications severity | Icinga 2 state | EvoHub severity | | --- | --- | --- | | critical (older releases: emerg, alert, crit) | CRITICAL, DOWN | critical | | error (older releases: err) | — | high | | warning | WARNING | medium | | notice (older releases) | UNKNOWN | low | | info, debug (older releases) | — | info | ## Resolve and deduplication An alert is identified by the object (Icinga Notifications) or by the host and service (Icinga 2). Icinga sends one notification per notified contact; any repeats while the alert is open are recorded as **Retriggered** and nobody is paged again. The alert resolves when the incident recovers (Icinga Notifications) or on the **RECOVERY** notification (Icinga 2). ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Checkmk](https://docs.evohub.io/checkmk.md) --- Source: https://docs.evohub.io/librenms.md # LibreNMS EvoHub receives LibreNMS alerts through an alert transport of type **API**. A rule that fires opens an EvoHub alert; its recovery resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **LibreNMS**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create the transport In LibreNMS, go to **Alerts → Alert Transports → Create alert transport** and choose the type **API**. Set **API Method** to `POST` and **API URL** to your webhook URL **without** the `?key=…` part, for example `https://evohub.io/ingest/librenms`. Put the key in **Options** as `key=YOUR_INTEGRATION_KEY` (the part of the webhook URL after `key=`). Set **Headers** to `Content-Type=application/json` and paste the body below on one line. ### Test it Click **Test**. LibreNMS sends its test alert; EvoHub answers with success and opens no alert. ### Use it in rules Attach the transport to your alert rules (or make it a default transport) and keep **Recovery alerts** on in each rule. Without it LibreNMS never tells EvoHub the alert has cleared. ::: ```json {"title":{{ $title|json_encode }},"rule":{{ $name|json_encode }},"rule_id":{{ $rule_id|json_encode }},"alert_id":{{ $alert_id|json_encode }},"device_id":{{ $device_id|json_encode }},"hostname":{{ $hostname|json_encode }},"sysName":{{ $sysName|json_encode }},"ip":{{ $ip|json_encode }},"os":{{ $os|json_encode }},"location":{{ $location|json_encode }},"severity":{{ $severity|json_encode }},"state":{{ $state|json_encode }},"timestamp":{{ $timestamp|json_encode }}} ``` > [!WARNING] > Keep the key out of the **API URL**. LibreNMS's API transport throws away everything after the `?` in the URL and sends the **Options** as the query string instead, so a key typed into the URL never reaches EvoHub and every alert is refused with `401`. > [!NOTE] > `|json_encode` writes each value as a quoted, escaped JSON string, which is why the placeholders have no quotes around them. The filter exists from LibreNMS 25.7. On an older LibreNMS write each value in quotes without the filter — `"title":"{{ $title }}"` — and EvoHub repairs line breaks, quotes and backslashes that end up inside values. The same fields also work with **Send as form**. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `title`, or the rule name and host name. | | Description | The rule and the device (host name, sysName, IP). | | Labels | `hostname`, `sysName`, `ip`, `os`, `location`, `rule`, `rule_id`, `device_id`, `severity`, `state`, `timestamp`. | ### Severity | LibreNMS severity | EvoHub severity | | --- | --- | | critical | critical | | warning | medium | ## Resolve and deduplication An alert is identified by the rule and the device. States *active*, *worse*, *better* and *changed* open the alert or are recorded as **Retriggered** on it; *recovered* (state `0`) resolves it. Acknowledging the alert in LibreNMS (state `2`) changes nothing in EvoHub. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [ManageEngine OpManager](https://docs.evohub.io/manageengine-opmanager.md) --- Source: https://docs.evohub.io/fortigate.md # FortiGate EvoHub receives FortiGate events through an **automation stitch**: a trigger on the event logs you care about, and a **Webhook** action that sends the log to EvoHub. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **FortiGate**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create the action In FortiOS, go to **Security Fabric → Automation**, create an **Action** of type **Webhook** with protocol **HTTPS**, method **POST**, your webhook URL, and the HTTP body `devname=FGT-HQ-01 %%log%%`, with `FGT-HQ-01` replaced by this FortiGate's hostname. FortiGate replaces `%%log%%` with the triggering log line in FortiOS's own `key=value` format (`date=… logid="0100032002" … msg="…"`), and EvoHub reads it as it is. That log line does not name the device, so the `devname=` in front of it is what keeps two FortiGates' alerts apart; without it, the same event from every FortiGate on this integration lands on one alert. To keep the key out of the URL, remove `?key=…` and add an HTTP header with key `Authorization` and value `Bearer YOUR_INTEGRATION_KEY` instead. ### Create the trigger Create a **Trigger** of type **FortiOS Event Log** and pick the log IDs that should page — for example administrator login failures, HA failover or VPN tunnel down. ### Create the stitch Create a **Stitch** that runs the action when the trigger fires. ::: The same action from the CLI: ```text config system automation-action edit "evohub" set action-type webhook set protocol https set method post set uri "evohub.io/ingest/fortigate" set port 443 set http-body "devname=FGT-HQ-01 %%log%%" config http-headers edit 1 set key "Authorization" set value "Bearer YOUR_INTEGRATION_KEY" next end next end ``` Without the header, put the key in the URI instead: `set uri "evohub.io/ingest/fortigate?key=YOUR_INTEGRATION_KEY"`. > [!WARNING] > Every log that matches the trigger is one delivery. Trigger on specific event log IDs, not on traffic logs. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `logdesc` and the device name (`devname`), for example *Admin login failed on FGT-HQ-01*. | | Description | `msg`. | | Labels | `devname`, `devid`, `vd`, `logid`, `type`, `subtype`, `level`, `eventtype`, `action`, `user`, `srcip`, `dstip`, `srcport`, `dstport`, `policyid`, `ui`, `status`, `reason`, `time`. | ### Severity | FortiOS log level | EvoHub severity | | --- | --- | | emergency, alert, critical | critical | | error | high | | warning | medium | | notice | low | | information, debug | info | ## Resolve and deduplication An alert is identified by the FortiGate (`devname` from the HTTP body, else `devid`) and the log ID. While it is open, the same event again is recorded as **Retriggered** instead of paging again. Log entries are events and FortiGate sends no recovery, so alerts stay open until someone resolves them in EvoHub. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Wazuh](https://docs.evohub.io/wazuh.md) --- Source: https://docs.evohub.io/github.md # GitHub EvoHub receives GitHub events through a repository or organization **webhook**. It pages on what breaks your default branch — failed workflow runs and deployments — and on security alerts, and resolves them when GitHub reports them fixed. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **GitHub**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Generate a signing secret Open the integration and, under **Signing secret**, click **Generate secret**. Copy it — it is shown once. ### Add the webhook In GitHub, open the repository (or organization) **Settings → Webhooks → Add webhook**. Set **Payload URL** to your webhook URL, **Content type** to `application/json`, and **Secret** to the signing secret. ### Choose the events Pick **Let me select individual events** and tick **Workflow runs**, **Deployment statuses**, and any of **Dependabot alerts**, **Code scanning alerts** and **Secret scanning alerts** that should page. Click **Add webhook**. ::: GitHub sends a **ping** when the webhook is created. EvoHub answers it as a test: `200`, no alert. ## What pages | GitHub event | Opens an alert when | Severity | Resolves when | | --- | --- | --- | --- | | Workflow runs | a run on the repository's default branch completes with `failure`, `timed_out` or `startup_failure` | high | the next run of the same workflow on that branch succeeds | | Deployment statuses | a deployment ends in `failure` or `error` | critical for a production environment, high otherwise | the next deployment to the same environment succeeds | | Dependabot alerts | an alert is created, reopened or reintroduced | the advisory's severity (critical, high, medium, low) | the alert is fixed, dismissed or withdrawn | | Code scanning alerts | an alert is created or reopened on the default branch, or appears there (a branch with the finding is merged) | the rule's security severity, or error → high, warning → medium, note → low | the alert is fixed on the default branch or closed | | Secret scanning alerts | an alert is created, reopened or found publicly leaked | critical | the alert is resolved on GitHub | A production environment is one GitHub marks as production, or one named `production`, `prod`, `prd` or `live`. Runs on other branches, cancelled or skipped runs, findings on pull request branches, events with no repository (organization, membership and similar events of an organization webhook) and every other event are answered `200` and open nothing. EvoHub never reads or stores the secret a secret scanning alert is about — only its type, its validity and the link to the alert. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | The workflow and branch, the environment, or the alert's summary, with the repository — for example *CI failed on main (acme/shop)*. | | Labels | `repository`, `event`, and per event `workflow`, `branch`, `conclusion`, `commit`, `environment`, `package`, `ghsa`, `cve`, `rule`, `tool`, `secret_type`, `validity`, and `url` — a link to the run, deployment log or alert. | ## Resolve and deduplication An alert is identified by the repository and the workflow and branch, the environment, or the alert number. While it is open, the same failure again is recorded as **Retriggered** instead of paging again. ## Signature With a signing secret set, every delivery must carry a valid `X-Hub-Signature-256` header (an HMAC-SHA256 of the body). A missing or wrong signature is refused with `403` and opens nothing. See [Signing secrets](https://docs.evohub.io/integrations-overview.md#signing-secrets). ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [GitLab](https://docs.evohub.io/gitlab.md) - [Jenkins](https://docs.evohub.io/jenkins.md) - [SonarQube](https://docs.evohub.io/sonarqube.md) --- Source: https://docs.evohub.io/gitlab.md # GitLab EvoHub receives GitLab events through a project or group **webhook**, on GitLab.com or your own GitLab. It pages when the default branch's pipeline or a deployment fails, and resolves when the next one succeeds. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **GitLab**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Generate a signing secret Open the integration and, under **Signing secret**, click **Generate secret**. Copy it — it is shown once. ### Add the webhook In GitLab, open the project (or group) **Settings → Webhooks → Add new webhook**. Set **URL** to your webhook URL and **Secret token** to the signing secret. ### Choose the triggers Tick **Pipeline events** or **Job events**, and **Deployment events** if you deploy with GitLab environments. Click **Add webhook**. ::: Pick **Pipeline events** for one page per failed pipeline, or **Job events** for one page per failed job. Ticking both pages for both. GitLab's **Test** button replays your latest event of that kind, so testing with a failed default-branch pipeline opens a real alert. > [!TIP] > On GitLab 19.1 and later a webhook can have a **Signing token** (`whsec_…`) instead of a secret token. Paste that value into **Use my own** under the integration's signing secret; EvoHub checks the signature and refuses deliveries more than five minutes old. ## What pages | GitLab event | Opens an alert when | Severity | Resolves when | | --- | --- | --- | --- | | Pipeline events | a pipeline on the project's default branch fails | high | the next pipeline on that branch succeeds | | Job events | a job on the default branch fails and is not allowed to fail | high | the same job on that branch succeeds | | Deployment events | a deployment fails | critical for the production tier, high otherwise | the next deployment to the same environment succeeds | Child pipelines are left to their parent, which fails with them. Tags, other branches, pushes, merge requests and every other event are answered `200` and open nothing — GitLab disables a webhook after four failed deliveries in a row, so EvoHub only refuses a malformed body, a wrong key or a wrong secret. GitLab has no webhook for its own alerts, so those are not received. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | What failed, where, and the project path — for example *Pipeline failed on main (acme/shop)*. | | Description | The pipeline number, the job and stage with GitLab's failure reason, or the deployment and its commit title. | | Labels | `project`, `event`, `ref`, `commit`, and per event `pipeline_source`, `job`, `stage`, `failure_reason`, `environment`, `environment_tier`, and `url` — a link to the pipeline, job or deployment job. | ## Resolve and deduplication An alert is identified by the project and the branch, the branch and job, or the environment. While it is open, another failure is recorded as **Retriggered** instead of paging again. ## Signature With a signing secret set, every delivery must carry it as `X-Gitlab-Token`, or be signed with it in `webhook-signature`. Anything else is refused with `403` and opens nothing. See [Signing secrets](https://docs.evohub.io/integrations-overview.md#signing-secrets). ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [GitHub](https://docs.evohub.io/github.md) - [Jenkins](https://docs.evohub.io/jenkins.md) --- Source: https://docs.evohub.io/jenkins.md # Jenkins EvoHub receives Jenkins builds in one JSON shape, sent either by the **Notification** plugin or by a `curl` step in a pipeline. A failed build pages; the next successful build of the same job and branch resolves it. ## Option A: Notification plugin :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Jenkins**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Install the plugin In Jenkins, go to **Manage Jenkins → Plugins** and install **Notification**. ### Add the endpoint Open the job's **Configure → Job Notifications → Add Endpoint**. Set **Format** to JSON, **Protocol** to HTTP, **Event** to *Job Completed*, and **URL** to your webhook URL. Do not use *Job Failed and First Success*: it never sends a green build that follows an UNSTABLE one, so the alert stays open. Avoid *All Events* too: it sends every finished build twice. ::: The plugin cannot send headers. To use a signing secret with it, generate one in EvoHub and put it into the URL as a password: `https://evohub:@` followed by the rest of the URL. A generated secret contains only letters and digits, so it needs no escaping. ## Option B: curl from a Jenkinsfile Store the webhook URL and the signing secret as Jenkins credentials exposed as `EVOHUB_URL` and `EVOHUB_SECRET`, then add: ```groovy post { failure { sh '''curl -fsS -X POST "$EVOHUB_URL" -H 'Content-Type: application/json' -H "X-EvoHub-Token: $EVOHUB_SECRET" -d "{\\"name\\":\\"$JOB_NAME\\",\\"build\\":{\\"number\\":$BUILD_NUMBER,\\"status\\":\\"FAILURE\\",\\"full_url\\":\\"$BUILD_URL\\",\\"scm\\":{\\"branch\\":\\"$BRANCH_NAME\\"}}}"''' } fixed { sh '''curl -fsS -X POST "$EVOHUB_URL" -H 'Content-Type: application/json' -H "X-EvoHub-Token: $EVOHUB_SECRET" -d "{\\"name\\":\\"$JOB_NAME\\",\\"build\\":{\\"number\\":$BUILD_NUMBER,\\"status\\":\\"SUCCESS\\",\\"full_url\\":\\"$BUILD_URL\\",\\"scm\\":{\\"branch\\":\\"$BRANCH_NAME\\"}}}"''' } } ``` `fixed` runs on the first green build after a failure, which is what resolves the alert. ## What pages | Build result | EvoHub | | --- | --- | | `FAILURE` | opens a **high** alert | | `UNSTABLE` | opens a **medium** alert | | `SUCCESS` | resolves the alert of the same job and branch | | `ABORTED`, `NOT_BUILT` | nothing | The **COMPLETED** and **FINALIZED** phases count: *Job Completed* sends COMPLETED, while *Job Failed* and *Job Failed and First Success* send only FINALIZED. With *All Events* a build sends both, and the second is recorded as **Retriggered**. QUEUED and STARTED are answered `200` and do nothing; a body with a result and no phase, like the curl step's, counts as completed. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | Job, build number, result and branch — for example *shop #42 FAILURE on origin/main*. | | Labels | `job`, `branch`, `status`, `commit`, and `url` — the build's `full_url`. | ## Resolve and deduplication An alert is identified by the job's full name (including its folders, as in `folder/job`) and the branch (`build.scm.branch`). While it is open, another failure is recorded as **Retriggered** instead of paging again. ## Signature With a signing secret set, every delivery must carry it — as the Basic-auth password in the URL or in the `X-EvoHub-Token` header. Anything else is refused with `403` and opens nothing. See [Signing secrets](https://docs.evohub.io/integrations-overview.md#signing-secrets). ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [GitHub](https://docs.evohub.io/github.md) - [GitLab](https://docs.evohub.io/gitlab.md) --- Source: https://docs.evohub.io/sonarqube.md # SonarQube EvoHub receives SonarQube analyses through a **webhook**. A failed quality gate on the main branch opens an alert listing the conditions that failed; the next passing gate resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **SonarQube**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Generate a signing secret Open the integration and, under **Signing secret**, click **Generate secret**. Copy it — it is shown once. ### Add the webhook In SonarQube, open **Project Settings → Webhooks** (or the instance-wide **Webhooks** page under **Administration** for every project) and click **Create**. Set **URL** to your webhook URL and **Secret** to the signing secret. ::: ## What pages | Analysis | EvoHub | | --- | --- | | Quality gate `ERROR` on the main branch | opens a **medium** alert | | Quality gate `OK` on the main branch | resolves it | | Pull request or other-branch analysis | nothing | | Analysis without a quality gate (the background task failed) | nothing | On editions without branch analysis there is only the main branch, and every analysis counts. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | *Quality gate failed:* and the project name and branch. | | Description | Each failed condition: the metric, its value and the threshold it crossed. | | Labels | `project`, `branch`, `quality_gate`, `revision`, and `url` — the project or branch dashboard. | ## Resolve and deduplication An alert is identified by the project key and branch. While it is open, another failed analysis is recorded as **Retriggered** instead of paging again. SonarQube does not retry a delivery that failed (or took longer than 10 seconds). If EvoHub could not be reached, that analysis's result is lost; the next analysis of the branch reports the quality gate again. Delivery results are listed under **Project Settings → Webhooks**. ## Signature With a signing secret set, every delivery must carry a valid `X-Sonar-Webhook-HMAC-SHA256` header (an HMAC-SHA256 of the body). Anything else is refused with `403` and opens nothing. See [Signing secrets](https://docs.evohub.io/integrations-overview.md#signing-secrets). ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [GitHub](https://docs.evohub.io/github.md) - [GitLab](https://docs.evohub.io/gitlab.md) --- Source: https://docs.evohub.io/new-relic.md # New Relic EvoHub receives New Relic issues through a **Webhook** destination that an alert workflow notifies. An active issue opens an EvoHub alert, and the issue closing resolves it. New Relic's default payload template works as it is. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **New Relic**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a webhook destination In New Relic, go to **Alerts → Destinations** and add a **Webhook**. Set **Endpoint URL** to your webhook URL: ```text https://evohub.io/ingest/newrelic?key=YOUR_INTEGRATION_KEY ``` ### Create a workflow Go to **Alerts → Workflows**, create a workflow for the policies that should page, and notify through the webhook destination. Leave the payload template as it is. ::: ## Turn on auto-resolve No extra setup is needed. When the workflow sends the update for an issue that is closed or deactivated, EvoHub resolves the matching alert. If the workflow only sends updates for new issues, alerts stay open until someone resolves them in EvoHub. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `title`, else the first alert condition name, else "New Relic Alert". | | Description | "Policy: " and the first alert policy name. | | Labels | `policy` and `condition` (the first policy and condition names). | | Link | `issueUrl` from the default template, the issue in New Relic. It opens from the alert. | | Fingerprint | The issue ID (`issueId`, or `id` in the default template). | ### Severity | New Relic priority | EvoHub severity | | --- | --- | | CRITICAL | critical | | HIGH | high | | MEDIUM | medium | | LOW | low | | missing or anything else | medium | ## Resolve and deduplication - An issue whose `state` is **CLOSED** or **DEACTIVATED** (or **RESOLVED**, for templates that send it) resolves the open alert for that issue ID. Any other state opens an alert. - Further updates for an issue whose alert is still open, such as **ACTIVATED** after **CREATED**, are recorded as **Retriggered**, so one issue is one EvoHub alert. ## Troubleshooting - **`401 INVALID_KEY`**: the URL's key is wrong or the integration is disabled. Copy the **Webhook URL** again from the integration. - **Nothing arrives**: check that the workflow's filter matches the issues and that it notifies the webhook destination. - **`400 INVALID_BODY`**: the payload template was changed so that it no longer renders a JSON object. Go back to the default template. - **One alert for several issues**: a custom template that leaves out the issue ID makes EvoHub key the alert by its title. Keep `issueId` (or the default `id`) in the payload. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Datadog](https://docs.evohub.io/datadog.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/azure-monitor.md # Azure Monitor EvoHub receives Azure Monitor alerts through a **Webhook** action in an action group, using the **common alert schema**. A fired alert opens an EvoHub alert, and the resolved notification resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Azure Monitor**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Open an action group In Azure Monitor, go to **Alerts → Action groups** and create or edit one. ### Add a webhook action Add an action of type **Webhook**. Set **URI** to your webhook URL and **Enable common alert schema** to **Yes**. ```text https://evohub.io/ingest/azuremonitor?key=YOUR_INTEGRATION_KEY ``` ### Attach the action group Attach the action group to the alert rules that should page. ::: > [!WARNING] > **Enable common alert schema** must be **Yes**. EvoHub refuses any other payload with `400 INVALID_SCHEMA` and the message "enable 'common alert schema' on the Azure action group". ## Turn on auto-resolve No extra setup is needed. When Azure Monitor sends the alert with `monitorCondition: Resolved`, EvoHub resolves the matching alert. If an alert rule never sends a resolved notification, its alerts stay open until someone resolves them in EvoHub. ## What EvoHub reads EvoHub reads the `data.essentials` part of the common alert schema: | EvoHub alert | Taken from | | --- | --- | | Title | `alertRule` (or "Azure Monitor Alert"). | | Description | `description`. A metric alert without one says what crossed what, from `alertContext`, for example "Percentage CPU is 31.1 (GreaterThan 25)". | | Labels | `severity` (as Azure sends it, for example `Sev1`) and `resource`, the first of `configurationItems`, plus the action group's `customProperties`. | | Link | `investigationLink` when Azure sends it, else a log search alert's link to its results. It opens from the alert. | | Values | A metric alert's `metric`, `metric_value` and `threshold`. | | Fingerprint | `alertId` (or, if it is missing, the alert rule name). | ### Severity | Azure severity | EvoHub severity | | --- | --- | | Sev0 | critical | | Sev1 | high | | Sev2 | medium | | Sev3 | low | | Sev4 | info | | anything else | medium | ## Resolve and deduplication - `monitorCondition: Resolved` resolves the open alert with the same `alertId`. Any other condition, such as **Fired**, opens an alert. - Another notification for the same `alertId` while its alert is open is recorded as **Retriggered**. ## Troubleshooting - **`400 INVALID_SCHEMA`**: the action group sends the legacy schema. Turn on **Enable common alert schema** for the webhook action. - **`401 INVALID_KEY`**: the URL's key is wrong or the integration is disabled. Copy the **Webhook URL** again from the integration. - **Nothing arrives**: check that the action group is attached to the alert rule and that the webhook action is the one with your URL. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Google Cloud Monitoring](https://docs.evohub.io/google-cloud-monitoring.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/aws-cloudwatch.md # AWS CloudWatch EvoHub receives CloudWatch alarms through an **Amazon SNS topic** with an HTTPS subscription. An alarm that goes to **ALARM** opens an EvoHub alert; the same alarm returning to **OK** resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **AWS CloudWatch**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create the SNS topic In Amazon SNS, create a **Standard** topic (or use one you have). ### Subscribe EvoHub to the topic Add a subscription to the topic with **Protocol** `HTTPS` and **Endpoint** your webhook URL. Leave **Raw message delivery** off: EvoHub needs the SNS envelope to check its signature. ### Check the subscription EvoHub confirms the subscription by itself. In SNS, check that the subscription shows an ARN rather than *PendingConfirmation*. ### Point your alarms at the topic In each CloudWatch alarm, set the topic as the **In alarm** action **and** the **OK** action. Without the OK action CloudWatch never tells EvoHub the alarm has cleared. ::: > [!NOTE] > EvoHub checks the SNS signature on every message against Amazon's signing certificate, so only messages that really come from Amazon SNS are accepted; anything else is refused with `403`. If the certificate cannot be fetched at that moment, EvoHub answers `503` and SNS delivers the message again. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `AlarmName` (else the SNS subject). | | Description | `AlarmDescription` and `NewStateReason` (what crossed the threshold), each when set. | | Labels | `state` — the alarm's new state — and each of the `Trigger`'s dimensions, for example `InstanceId`. | | Link | The alarm in the CloudWatch console, in the region named by `AlarmArn`. It opens from the alert. | | Values | `metric` (`Namespace`/`MetricName`) and `threshold` (`ComparisonOperator` and `Threshold`). | ### Severity | Alarm state | EvoHub severity | | --- | --- | | `ALARM` | critical | | `INSUFFICIENT_DATA` | low | | `OK` | resolves the alert | `INSUFFICIENT_DATA` only reaches EvoHub if you also set the topic as the alarm's **Insufficient data** action; leave that action empty unless a metric that stops reporting should open a low alert. ## Resolve and deduplication An alert is identified by the AWS account, the region and the alarm name (plus the contributor, for a multi-time-series alarm, so each contributor is its own alert and its title names the contributor), so identically named alarms in two accounts or regions stay two alerts. While it is open, the alarm firing again is recorded as **Retriggered** instead of paging again; `OK` resolves it. One SNS topic can carry any number of alarms. ## CloudTrail CloudTrail activity — a root sign-in, logging turned off — pages through this same integration, with a metric filter and an alarm. See [AWS CloudTrail](https://docs.evohub.io/aws-cloudtrail.md). ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [AWS EventBridge](https://docs.evohub.io/aws-eventbridge.md) - [AWS CloudTrail](https://docs.evohub.io/aws-cloudtrail.md) --- Source: https://docs.evohub.io/dynatrace.md # Dynatrace EvoHub receives Dynatrace problems through a **Custom integration** problem notification. An open problem opens an EvoHub alert, and the resolved notification resolves it. The custom payload Dynatrace fills in for you works as it is. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Dynatrace**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a problem notification In Dynatrace, go to **Settings → Integration → Problem notifications** (under **Settings Classic** in the latest Dynatrace), click **Add notification** and choose **Custom integration**. ### Set the URL Set **Webhook URL** to your webhook URL and leave the pre-filled **Custom payload** as it is. The `{…}` parts are Dynatrace placeholders. ```text https://evohub.io/ingest/dynatrace?key=YOUR_INTEGRATION_KEY ``` ### Add severity and details (optional) For a better severity and a fuller description, add these two lines to the payload: ```json "ProblemSeverity": "{ProblemSeverity}", "ProblemDetailsText": "{ProblemDetailsText}", ``` ::: The pre-filled payload contains `{ImpactedEntities}` and `{ProblemDetailsJSON}` without quotes, so they arrive as JSON arrays and objects. EvoHub reads that as it is; you do not need to quote them. ## Turn on auto-resolve Nothing extra is needed. Dynatrace sends the notification again with `State` **RESOLVED** when the problem closes, and EvoHub resolves the matching alert. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | `ProblemTitle` (or "Dynatrace Problem"). | | Description | `ProblemDetailsText`; without it, the problem's URL (`ProblemURL` or `Problem URL`). | | Link | The problem's URL, whatever the description. It opens from the alert. | | Labels | `impact` (`ProblemImpact`), `impacted_entity` (`ImpactedEntityNames` or `ImpactedEntity`) and `tags` (`Tags`), when sent. | | Fingerprint | `PID`, the problem's system-wide ID; without it, `ProblemID`. | ### Severity Severity comes from `ProblemSeverity`. Add it to the payload as shown above; without it every problem is **medium**. | Dynatrace severity | EvoHub severity | | --- | --- | | AVAILABILITY | critical | | ERROR | high | | MONITORING_UNAVAILABLE | high | | PERFORMANCE, RESOURCE_CONTENTION | medium | | CUSTOM_ALERT | low | | INFO | info | | missing or anything else | medium | ## Resolve and deduplication - `State` **RESOLVED** resolves the open alert with the same `PID`. Any other state, such as **OPEN**, opens an alert. - Further notifications for a problem whose alert is still open are recorded as **Retriggered**. ## Troubleshooting - **`401 INVALID_KEY`**: the URL's key is wrong or the integration is disabled. Copy the **Webhook URL** again from the integration. - **`400 INVALID_BODY`**: the edited payload is no longer valid JSON. A missing comma after a line you added is the usual cause. - **Every alert is medium**: add `"ProblemSeverity": "{ProblemSeverity}"` to the payload. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [New Relic](https://docs.evohub.io/new-relic.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/sentry.md # Sentry EvoHub receives Sentry alerts through an **Internal** custom integration. Issue alerts and metric alerts both open EvoHub alerts. An issue's alert resolves when the issue is resolved, archived or ignored in Sentry, and a metric alert's resolves when Sentry reports it resolved. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Sentry**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create an internal integration In Sentry's organization **Settings**, open **Developer Settings** (custom integrations) and create a new **Internal** integration. ### Set the URL and webhooks Set **Webhook URL** to your webhook URL, enable **Alert Rule Action**, and under **Webhooks** check **issue**. ```text https://evohub.io/ingest/sentry?key=YOUR_INTEGRATION_KEY ``` ### Use it in alert rules In each issue alert or metric alert rule that should page, add the action that sends a notification through your integration. ::: The legacy Sentry **Webhooks** plugin is not supported; use an internal integration. > [!NOTE] > With **issue** checked, Sentry also sends every newly created issue to the integration, and each one opens an EvoHub alert whether or not an alert rule matched it. That webhook is also how issue alerts resolve. ## Turn on auto-resolve - **Issue alerts** resolve through the **issue** webhook: when the issue is **resolved**, **archived** or **ignored** in Sentry, EvoHub resolves its alert. Keep **issue** checked. - **Metric alerts** resolve on their own: the alert rule's **resolved** notification resolves the alert. ## What EvoHub reads ### Issue alerts and issue webhooks | EvoHub alert | Taken from | | --- | --- | | Title | The issue's (or event's) `title`, or "Sentry Issue". | | Description | The `culprit`. | | Labels | `level`; for an issue alert, `rule` (`triggered_rule`) and the event's `environment` and `release` when set; for an issue webhook, `project` and `short_id`. | | Link | The event's or the issue's `web_url`. It opens from the alert. | | Fingerprint | The issue ID: `data.issue.id`, or `data.event.issue_id` for an issue alert, so the alert rule's notification and the issue webhook meet on the same alert. | ### Metric alerts | EvoHub alert | Taken from | | --- | --- | | Title | `description_title`, else the alert rule name, else the incident title. | | Description | `description_text`. | | Labels | `alert_type` (`metric_alert`), `action` (`critical` or `warning`) and `url` (`web_url`). | | Fingerprint | The metric alert incident's ID, so each incident is one alert. | ### Severity | Sentry | EvoHub severity | | --- | --- | | Issue level `fatal` | critical | | Issue level `error` | high | | Issue level `warning` | medium | | Issue level `info` | info | | Issue level `debug` | low | | Issue level missing or other | medium | | Metric alert `critical` | critical | | Metric alert `warning` | high | ## Resolve and deduplication - An issue webhook with the action **resolved**, **archived** or **ignored** resolves the open alert for that issue. **assigned** changes nothing. **created** and **unresolved** open an alert. - A metric alert's **resolved** notification resolves that incident's alert. A **warning** followed by **critical** for the same incident stays one alert, recorded as **Retriggered**. - Repeated notifications for an issue whose alert is still open are recorded as **Retriggered**. - A delivery with no issue, event or metric alert in it (an installation webhook, for example) is answered with success and opens nothing. ## Troubleshooting - **`401 INVALID_KEY`**: the URL's key is wrong or the integration is disabled. Copy the **Webhook URL** again from the integration. - **Issue alerts never resolve**: **issue** is not checked under **Webhooks** in the internal integration. - **An alert for every new issue**: that is the **issue** webhook. Remove the check if you only want alert rules to page — issue alerts then no longer resolve automatically. - **Nothing arrives from an alert rule**: the rule has no action for your integration, or the integration's **Alert Rule Action** is off. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Datadog](https://docs.evohub.io/datadog.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/google-cloud-monitoring.md # Google Cloud Monitoring EvoHub receives Google Cloud Monitoring incidents through a **Webhooks** notification channel. An incident opening creates an EvoHub alert, and the incident closing resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Google Cloud**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a webhook channel In Google Cloud, go to **Monitoring → Alerting → Edit notification channels → Webhooks → Add new** and set **Endpoint URL** to your webhook URL. ```text https://evohub.io/ingest/googlecloud?key=YOUR_INTEGRATION_KEY ``` ### Use it in alerting policies Select the channel in the alerting policies that should page, and turn on the policy's option to notify when the incident closes. ::: ## Turn on auto-resolve By default, Cloud Monitoring notifies only when an incident opens. In each alerting policy, turn on the option to notify on closure (shown as **Notify on incident closure** or **Notify on alert closure**, depending on the console version). Cloud Monitoring then notifies the channel with the incident's `state` set to `closed`, and EvoHub resolves the matching alert. Without it, alerts stay open until someone resolves them in EvoHub. ## What EvoHub reads EvoHub reads the `incident` object of the notification: | EvoHub alert | Taken from | | --- | --- | | Title | `policy_name`, else `condition_name`, else "Google Cloud Alert". | | Description | `summary`. | | Labels | `policy` and `condition`. | | Link | `url`, the incident in the Cloud console. It opens from the alert. | | Values and notes | `observed_value`, `threshold_value`, and the policy's documentation (`documentation`) — often its runbook. | | Fingerprint | `incident_id`. | ### Severity Severity comes from the severity level set on the alerting policy: | Policy severity | EvoHub severity | | --- | --- | | CRITICAL | critical | | ERROR | high | | WARNING | medium | | none set, or anything else | info | A policy without a severity level opens **info** alerts. Set a severity on policies that should wake someone up. ## Resolve and deduplication - `state: closed` resolves the open alert with the same `incident_id`. Any other state opens an alert. - Another notification for the same incident while its alert is open is recorded as **Retriggered**. A new incident from the same policy has a new ID and opens a new alert. ## Troubleshooting - **`401 INVALID_KEY`**: the URL's key is wrong or the integration is disabled. Copy the **Webhook URL** again from the integration. - **Alerts arrive as info**: the alerting policy has no severity level. Set one in the policy. - **Nothing arrives**: check that the channel is selected in the policy's notifications. - **Alerts never resolve**: the policy does not notify on incident closure. Turn that option on in the policy's notifications. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Azure Monitor](https://docs.evohub.io/azure-monitor.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/uptimerobot.md # UptimeRobot EvoHub receives UptimeRobot alerts through a **Webhook** integration. A monitor going down opens an EvoHub alert, and the monitor coming back up resolves it. SSL and domain expiry notices open an alert of their own. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **UptimeRobot**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a webhook In UptimeRobot, go to **Integrations → Add Integration → Webhook** and set **Webhook URL** to your webhook URL. ### Set the POST value Paste the POST value below as it is (the `*…*` parts are UptimeRobot variables), turn **Send as JSON** on and **Send as query string** off. ### Choose the events Under the events to notify about, tick **Down** and **Up**. Without **Up**, nothing ever resolves. **SSL & Domain expiry** is optional. ### Add it to your monitors Add the integration to the notifications of the monitors that should page. ::: Webhook URL: ```text https://evohub.io/ingest/uptimerobot?key=YOUR_INTEGRATION_KEY ``` POST value: ```json {"monitorID":"*monitorID*","monitorFriendlyName":"*monitorFriendlyName*","monitorURL":"*monitorURL*","alertType":"*alertType*","alertTypeFriendlyName":"*alertTypeFriendlyName*","alertDetails":"*alertDetails*","sslExpiryDate":"*sslExpiryDate*","sslExpiryDaysLeft":"*sslExpiryDaysLeft*"} ``` EvoHub reads the same variables whichever way UptimeRobot sends them: as this JSON body, as POST form values, or appended to the URL as a query string. Variables missing from the body are taken from the URL. ### Set up through Alert Contacts? An older setup made under **My Settings → Alert Contacts → Web-Hook**, with the URL ending in `&` (`…?key=YOUR_INTEGRATION_KEY&`) and no POST value, may not reach EvoHub. Without a POST value UptimeRobot only appends its variables to the URL, and such a webhook can be sent as a GET request ([UptimeRobot's webhooks were GET-only before POST values](https://uptimerobot.com/blog/post-support-for-web-hook-alert-contacts-now-available/)). EvoHub accepts POST only, so a GET is refused and the alert is lost. Add the POST value above to that webhook and turn **Send as JSON** on, or recreate it as described above. Then check that a test alert arrives in EvoHub. ## Turn on auto-resolve Tick **Up** among the events the integration notifies about. The Up notification resolves the alert that the Down notification opened. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | "`monitorFriendlyName` is Down", for example "Checkout API is Down" (or "UptimeRobot: Monitor Down"). | | Description | `alertDetails`, the reason the check failed. | | Labels | `url` (`monitorURL`); for expiry notices also `ssl_expiry_date` and `ssl_expiry_days_left`. | | Fingerprint | `monitorID`. Without it, the monitor's URL, else its name, else one fixed key for the integration. | Down alerts are always **critical**, and so are SSL and domain expiry notices. ### SSL and domain expiry An expiry notice (`alertType` 3) opens a separate alert, titled for example "Checkout API: SSL certificate expires in 14 days". It never resolves or merges into the monitor's down alert, and repeated notices for the same monitor are one alert. It does not resolve by itself either: resolve it in EvoHub once the certificate or domain is renewed. ## Resolve and deduplication - EvoHub tells Down from Up by `alertTypeFriendlyName` (**Down** or **Up**), then by the numeric `alertType` (`1` down, `2` up; the older `0` also means down). - An Up notification resolves the open alert for the same monitor. Another Down while the alert is open is recorded as **Retriggered**. - A delivery that is neither an Up nor an expiry notice is treated as **Down**, so a monitoring notification is never silently dropped. That includes one with no variables at all, which opens "UptimeRobot: Monitor Down". - Other bodies work too. UptimeRobot's own example body (`monitor`, `status`, `url`, `details`) is read when there is no `monitorID`. A custom body with only free text (`{"text": "..."}` or `{"message": "..."}`) opens an alert titled with its first line, and text such as "is up", "is back" or "recovered" resolves it. ## Troubleshooting - **`401 INVALID_KEY`**: the URL's key is wrong or the integration is disabled. In an **Alert Contacts** setup, a URL without the trailing `&` corrupts the key in the same way. - **`400 INVALID_BODY`**: the POST value is not valid JSON while **Send as JSON** is on. Paste the POST value above again. - **All monitors share one alert**: the POST value leaves out `monitorID`, `monitorURL` and `monitorFriendlyName`. Use the POST value above. - **Alerts never resolve**: **Up** is not ticked among the events. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Uptime Kuma](https://docs.evohub.io/uptime-kuma.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/pingdom.md # Pingdom EvoHub receives Pingdom alerts through a **Webhook** integration assigned to your checks. A check going down opens an EvoHub alert, and the check recovering resolves it. Uptime checks and transaction checks both work. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Pingdom**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a webhook integration In My Pingdom, go to **Integrations → Add integration**, choose **Webhook** and set **URL** to your webhook URL. ```text https://evohub.io/ingest/pingdom?key=YOUR_INTEGRATION_KEY ``` ### Connect it to each check Edit each check that should page, open **Connect integrations** and enable the webhook. ::: > [!WARNING] > The webhook sends nothing until it is connected to a check. Connect it to every check you want EvoHub to page for. ## Turn on auto-resolve Nothing extra is needed. When a check recovers, Pingdom sends the webhook with `current_state` **UP** (or **SUCCESS** for a transaction check), and EvoHub resolves the matching alert. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | "`check_name` is `current_state`", for example "Checkout is DOWN" (or "Pingdom Alert"). | | Description | `description`. | | Labels | `check_type`. | | Fingerprint | `check_id`. | ### Severity | Pingdom importance level | EvoHub severity | | --- | --- | | HIGH | critical | | LOW, or missing | medium | ## Resolve and deduplication - `current_state` **DOWN** (uptime checks) or **FAILING** (transaction checks) opens an alert. **UP** or **SUCCESS** resolves the open alert for that check. Other states change nothing. - Another down notification for a check whose alert is open is recorded as **Retriggered**. ## Test notifications Pingdom's test webhook opens a real alert, titled "Pingdom test message received" (with the check name when Pingdom sends one), under the integration's escalation policy, so the test exercises the whole path, paging included. The test alert is keyed to the check like a real one: if that check already has an open alert, the test is recorded on it as **Retriggered**, and the check's next recovery resolves it. Otherwise resolve it in EvoHub afterwards. ## Troubleshooting - **`401 INVALID_KEY`**: the URL's key is wrong or the integration is disabled. Copy the **Webhook URL** again from the integration. - **Nothing arrives**: the webhook is not connected to the check. Edit the check and enable it under **Connect integrations**. - **Everything is medium**: only checks whose importance level is **HIGH** open **critical** alerts. Set it on the checks that should wake someone up. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [UptimeRobot](https://docs.evohub.io/uptimerobot.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/site24x7.md # Site24x7 EvoHub receives Site24x7 alerts through a **Webhook** third-party integration. A monitor going Down, Critical or Trouble opens an EvoHub alert, and the monitor returning to Up resolves it. Site24x7's default incident parameters work as they are; no custom body is needed. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Site24x7**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a webhook In Site24x7, go to **Admin → Third-Party Integration → Add Third-Party Integration** and choose **Webhooks**. ### Set the URL and options Set **Hook URL** to your webhook URL and the HTTP method to **POST**, turn on **Send Incident Parameters**, and turn on **Post as JSON** (recommended; form mode also works). ```text https://evohub.io/ingest/site24x7?key=YOUR_INTEGRATION_KEY ``` ::: ## Turn on auto-resolve Nothing extra is needed. When a monitor is back up, Site24x7 sends `STATUS` **UP**, and EvoHub resolves the matching alert. ## What EvoHub reads EvoHub reads the default incident parameters, either as JSON (**Post as JSON** on) or as form values: | EvoHub alert | Taken from | | --- | --- | | Title | "`MONITORNAME` is `STATUS`", for example "Checkout is DOWN" (or "Site24x7 Alert"). | | Description | `INCIDENT_REASON`, else `INCIDENT_DETAILS`. | | Labels | `monitor_type` (`MONITORTYPE`) and `status`; when sent, `monitored` (`MONITORURL`), `group` (`MONITOR_GROUPNAME`) and `failed_locations` (`FAILED_LOCATIONS`). | | Links | `MONITOR_DASHBOARD_LINK` (the monitor in Site24x7) and `RCA_LINK` (its root cause analysis) as `rca_url`. They open from the alert. | | Fingerprint | `MONITOR_ID`; without it, the monitor name. | ### Severity | Status | Priority | EvoHub severity | | --- | --- | --- | | DOWN | any | critical | | CRITICAL | any | high | | TROUBLE | any | medium | | any other | HIGH | high | | any other | LOW | low | | any other | other or missing | medium | ## Resolve and deduplication - `STATUS` **UP** resolves the open alert for that monitor. Any other status opens an alert. - A monitor that goes from Trouble to Down keeps one alert: the second notification is recorded as **Retriggered**. ## Troubleshooting - **`401 INVALID_KEY`**: the URL's key is wrong or the integration is disabled. Copy the **Webhook URL** again from the integration. - **`400 INVALID_BODY`**: the body is neither JSON nor form values with any of `MONITORNAME`, `STATUS` or `MONITOR_ID`. Turn on **Send Incident Parameters**, or remove a custom body. - **Alerts never resolve**: check that the integration is notified when the monitor is up, not only when it goes down. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Pingdom](https://docs.evohub.io/pingdom.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/statuscake.md # StatusCake EvoHub receives StatusCake alerts through the **Webhook URL** of a contact group. A test going Down opens an EvoHub alert, and the test coming back Up resolves it. StatusCake sends a fixed form post on Down and Up, so there is no body to configure. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Statuscake**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Set the contact group webhook In StatusCake, go to **Alerting → Contact Groups**, create or edit one, set **Webhook URL** to your webhook URL and the webhook method to **POST**. ```text https://evohub.io/ingest/statuscake?key=YOUR_INTEGRATION_KEY ``` ### Assign the contact group Assign the contact group to the uptime tests that should page. ::: ## Turn on auto-resolve Nothing extra is needed. StatusCake posts `Status=Up` when a test recovers, and EvoHub resolves the matching alert. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | "`Name` is Down", for example "Checkout is Down" (or "Statuscake: Monitor Down"). | | Description | "`URL` is unreachable". | | Labels | `url`, `ip`, `status_code` and `tags`, when sent. | | Fingerprint | The test's `Name`; without it, its `URL`. | Every Down alert is **critical**. ## Resolve and deduplication - `Status=Down` opens an alert and `Status=Up` resolves the open alert for the same test. A post with any other status, or none, opens nothing. - StatusCake's webhook carries no test ID, so EvoHub keys alerts by the test's name. Two tests with the same name share one alert, and renaming a test while it is down leaves its alert open — give tests unique names, and resolve a renamed test's alert in EvoHub. - Another Down for a test whose alert is open is recorded as **Retriggered**. ## Troubleshooting - **`401 INVALID_KEY`**: the URL's key is wrong or the integration is disabled. Copy the **Webhook URL** again from the integration. - **Nothing arrives**: the contact group is not assigned to the test. - **One alert for several tests**: the tests have the same name. Rename them so each is unique. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [UptimeRobot](https://docs.evohub.io/uptimerobot.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/nagios.md # Nagios EvoHub receives Nagios notifications through two notification commands that post JSON with `curl`, one for services and one for hosts. A PROBLEM notification opens an EvoHub alert, and the RECOVERY notification resolves it. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Nagios**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add the notification commands Add the two commands below to `commands.cfg`, with your webhook URL in place of the example. The `$…$` parts are Nagios macros, filled in automatically. ### Use them on the contact On the contact your hosts and services notify, set the notification commands and options below, then reload Nagios. ::: Commands: ```text define command { command_name notify-evohub command_line /usr/bin/curl -s -X POST "https://evohub.io/ingest/nagios?key=YOUR_INTEGRATION_KEY" -H "Content-Type: application/json" -d '{"host":"$HOSTNAME$","service":"$SERVICEDESC$","state":"$SERVICESTATE$","notification_type":"$NOTIFICATIONTYPE$","output":"$SERVICEOUTPUT$"}' } define command { command_name notify-evohub-host command_line /usr/bin/curl -s -X POST "https://evohub.io/ingest/nagios?key=YOUR_INTEGRATION_KEY" -H "Content-Type: application/json" -d '{"host":"$HOSTNAME$","service":"","state":"$HOSTSTATE$","notification_type":"$NOTIFICATIONTYPE$","output":"$HOSTOUTPUT$"}' } ``` Contact settings: ```text service_notification_commands notify-evohub service_notification_options w,u,c,r host_notification_commands notify-evohub-host host_notification_options d,u,r ``` Check output goes into the JSON unescaped. EvoHub repairs what that breaks — stray backslashes (Windows paths such as `c:\`), double quotes and line breaks — so such alerts still arrive. Nagios itself strips the characters listed in `illegal_macro_output_chars` (quotes, by default). ## Turn on auto-resolve Keep the `r` in both notification options. It is what makes Nagios send RECOVERY notifications; without it nothing resolves. ## What EvoHub reads | EvoHub alert | Taken from | | --- | --- | | Title | "`host`/`service` is `state`" for a service, for example "web-01/HTTP is CRITICAL"; "Host `host` is `state`" for a host. | | Description | `output`, the check output. | | Labels | `host`, `state` and, for services, `service`. | | Fingerprint | The host and service together (the host alone for host alerts). | ### Severity | Nagios state | EvoHub severity | | --- | --- | | CRITICAL, DOWN, UNREACHABLE | critical | | WARNING | medium | | UNKNOWN | low | | anything else | medium | ## Resolve and deduplication - A **PROBLEM** notification opens an alert. A **RECOVERY** notification resolves the open alert for the same host and service. - **ACKNOWLEDGEMENT**, **FLAPPINGSTART**/**FLAPPINGSTOP** and **DOWNTIMESTART**/**DOWNTIMEEND** notifications are answered with success and change nothing. - A notification with an empty `notification_type` is treated as a problem, so keep `$NOTIFICATIONTYPE$` in the command — without it, a recovery would open an alert instead of resolving one. - Because the fingerprint is host plus service, repeated problem notifications are recorded as **Retriggered** on the open alert, and the same service on different hosts opens separate alerts. A service going from WARNING to CRITICAL stays one alert. ## Troubleshooting - **Nothing arrives**: run the `command_line` by hand with sample values and read EvoHub's answer. `INVALID_KEY` means the key is wrong or the integration is disabled. - **`INVALID_BODY`**: the body is not a JSON object. Check that the `-d '…'` part was copied whole. - **Alerts never resolve**: the contact's notification options are missing `r`. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Icinga](https://docs.evohub.io/icinga.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/cortex-xdr.md # Cortex XDR EvoHub receives security issues from Palo Alto Networks Cortex XDR and XSIAM, such as DLP alerts, through webhook forwarding. Cortex sends its own issue JSON, which EvoHub reads as it is. An issue opens an EvoHub alert, and the issue being resolved or closed resolves it. Requires Cortex XDR 5.x or later, or XSIAM 3.x or later. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Cortex XDR**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a webhook application In Cortex, go to **Settings → Configurations → Integrations → External Applications → Add Application → Webhook** and set **URL** to your webhook URL, then click **Verify**. Verification fails until egress to the webhook's host is allowed in the Cortex Gateway's egress configuration. ```text https://evohub.io/ingest/cortex?key=YOUR_INTEGRATION_KEY ``` ### Forward the issues Go to **Settings → Configurations → General → Notifications**, add a **Forwarding Configuration** and pick the issues to forward to the webhook. ::: ## Turn on auto-resolve Nothing extra is needed. When Cortex forwards an issue whose status is resolved or closed, EvoHub resolves the matching alert. ## What EvoHub reads Field names differ between Cortex generations and builds, so EvoHub looks for each value under the names Cortex has used: | EvoHub alert | Taken from | | --- | --- | | Title | `name`, else `issue_name`, `alert_name`, `incident_name`, `rule_name`, `display_name`, `title` or `headline`; else the description; else "Cortex Alert". | | Description | `description`, else `issue_description`, `alert_description`, `details`, `message`, `text` or `summary`. | | Labels | `domain` (`issue_domain`), `category`, and every other top-level text, number or true/false field as it is (for example `host_name` or `observation_time`), up to 40 labels. Nested objects and lists are left out, and long values are cut. | | Fingerprint | `incident_id` (XDR incidents), else `external_id`, else `id` (XSIAM issues); else the title. | ### Severity EvoHub reads `severity`, or `platform_severity` when `severity` is empty. The `SEV_050_CRITICAL` form (read by its last word), plain words and the older numbers 0–4 all work: | Cortex severity | EvoHub severity | | --- | --- | | `SEV_050_CRITICAL`, `critical`, `4` | critical | | `SEV_040_HIGH`, `high`, `3` | high | | `SEV_030_MEDIUM`, `medium`, `2` | medium | | `SEV_…_LOW`, `low`, `1` | low | | `SEV_…_INFO`, `info`, `informational`, `0` | info | | anything else | medium | ## Resolve and deduplication - An issue whose `status` (XDR) or `resolution_status` (XSIAM) contains **RESOLVED** or **CLOSED** resolves the open alert with the same ID — for example `resolved_threat_handled` or `STATUS_040_RESOLVED_AUTO`. Any other status, such as `new` or `STATUS_010_NEW`, opens an alert. - Further notifications for an issue whose alert is still open, such as a move to under investigation, are recorded as **Retriggered**. ## Troubleshooting - **`401 INVALID_KEY`**: the URL's key is wrong or the integration is disabled. Copy the **Webhook URL** again from the integration. - **Alerts are titled "Cortex Alert"**: your Cortex build sends the issue name under a field EvoHub does not know yet. The alert's labels show every field that arrived; open a ticket from **Help & Support** with the label names so the field can be mapped (see [Get help](https://docs.evohub.io/get-help.md)). - **Nothing arrives**: check that the forwarding configuration selects the issues and uses the webhook application. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Wazuh](https://docs.evohub.io/wazuh.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/opensearch.md # OpenSearch EvoHub receives OpenSearch Alerting notifications through a **Custom webhook** notification channel. Each notification from a monitor's trigger opens an EvoHub alert. The default action message works as it is; a short JSON template adds structured fields. ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **OpenSearch**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Create a channel In OpenSearch, go to **Notifications → Channels → Create channel** and choose **Custom webhook**. ### Set the URL Set **Define endpoint by** to **Webhook URL** and paste your webhook URL. ```text https://evohub.io/ingest/opensearch?key=YOUR_INTEGRATION_KEY ``` ### Use it in monitors Select the channel in the actions of your monitors' triggers. ::: > [!WARNING] > Use **Webhook URL**, not **Custom attributes URL**. In the custom attributes mode the `?key=` part is dropped, and every delivery is refused with `401`. ### Use the JSON template (optional) For richer fields, replace the trigger action's message with: ```json { "monitor": "{{ctx.monitor.name}}", "trigger": "{{ctx.trigger.name}}", "severity": "{{ctx.trigger.severity}}", "message": "{{ctx.trigger.name}}", "period_start": "{{ctx.periodStart}}", "period_end": "{{ctx.periodEnd}}" } ``` ## No auto-resolve EvoHub does not read a recovery from anything OpenSearch sends, so these alerts do not resolve automatically. Resolve them in EvoHub when the problem is handled. On a bucket-level monitor with per-alert actions, leave **Completed** out of the actionable alerts: EvoHub would read that notification as another alert, not a recovery. ## What EvoHub reads With the JSON template: | EvoHub alert | Taken from | | --- | --- | | Title | `trigger`, else `monitor`, else "OpenSearch Alert". | | Description | `message`. | | Labels | `monitor` and `trigger`. | | Fingerprint | The monitor and trigger names together. | With the default message ("Monitor … just entered alert status. Please investigate the issue." followed by `- Trigger:`, `- Severity:` and period lines), EvoHub reads the monitor name, the trigger and the severity from the text, and the whole message becomes the description. Title, labels and fingerprint are as above. ### Severity | OpenSearch trigger severity | EvoHub severity | | --- | --- | | 1 (highest) | critical | | 2 | high | | 3 | medium | | 4 | low | | 5 | info | | missing or anything else | medium | ## Resolve and deduplication - Another notification for the same monitor and trigger while its alert is open is recorded as **Retriggered**. Once you resolve the alert, the next notification opens a new one. - Nothing from OpenSearch resolves an alert. ## Test messages The channel's **Send test message** opens a real alert titled "OpenSearch test message received", severity **medium**, under the integration's escalation policy, so the whole path is exercised, paging included. It carries the label `test=true`. Repeated test messages while it is open are recorded as **Retriggered**. Resolve it in EvoHub afterwards. ## Troubleshooting - **`401 INVALID_KEY`**: the key is wrong or the integration is disabled — or the channel uses **Custom attributes URL**, which drops `?key=`. Switch **Define endpoint by** to **Webhook URL**. - **`400 INVALID_BODY`**: the action message is neither JSON nor OpenSearch's default text. Go back to the default message or paste the JSON template above. - **Several triggers of one monitor share one alert**: the JSON template leaves out `trigger`. Keep both `monitor` and `trigger`; together they tell alerts apart. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Elastic / Kibana](https://docs.evohub.io/elastic-kibana.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/koi.md # KOI EvoHub receives alerts from KOI (koi.ai, formerly Koi Security), which watches the extensions, packages, MCP servers and AI agents on your endpoints. KOI sends its alert JSON to a webhook, and EvoHub reads it as it is: an alert opens an EvoHub alert, and the alert being resolved, closed, dismissed or remediated in KOI resolves it. :::note **This mapping is provisional.** KOI's webhook payload is not publicly documented, so EvoHub reads it tolerantly: it looks for each value under several field names and keeps every field that arrives on the alert. If your alerts arrive with the wrong title or severity, send us a sample delivery (with any personal data removed) from **Help & Support** and we will map it exactly (see [Get help](https://docs.evohub.io/get-help.md)). ::: ## Set it up :::steps ### Create the integration In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **KOI**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**. ### Add a webhook in KOI In KOI's integration settings, add a webhook and paste your webhook URL as its address. Choose the alerts to send. ```text https://evohub.io/ingest/koi?key=YOUR_INTEGRATION_KEY ``` ### Send a test Send a test from KOI, or wait for the next alert. A delivery with no alert in it is answered with `"test": true` and pages nobody; the integration's **Last Event** still updates. ::: ## What EvoHub reads A delivery may be one alert object, a list of them, or either wrapped in `alerts`, `events`, `data`, `event` or `alert`. For each alert: | EvoHub alert | Taken from | | --- | --- | | Title | `title`, else `name`, `alert_name`, `rule_name`, `policy_name` or `description`; else "KOI alert", followed by the item's name when the alert carries one (`item_name`, `extension_name`, `package_name`, …). | | Description | `description`, else `message`, `details` or `summary`. | | Labels | Every field of the alert: nested objects as dotted names (for example `endpoint.hostname`), lists joined with commas. Long values are cut and at most 50 labels are kept. | | Fingerprint | `id`, else `alert_id`, `event_id` or `uuid`, together with the endpoint when the alert names one (`endpoint_id`, `device_id`, `host_id`, `machine_id`); else the title. | ### Severity EvoHub reads `severity`, else `risk_level`, `risk` or `priority`, ignoring case: | KOI value | EvoHub severity | | --- | --- | | `critical` | critical | | `high` | high | | `medium`, `moderate` | medium | | `low` | low | | `info`, `informational` | info | | anything else, numbers included | medium | ## Resolve and deduplication - An alert whose `status` is `resolved`, `closed`, `dismissed` or `remediated` resolves the open alert with the same fingerprint. Any other status, such as `open`, `new` or `active`, opens an alert. - Further deliveries for an alert that is still open are recorded as **Retriggered**. - Audit events (`source_log_type` `Audit`) are records of changes made in KOI, not alerts: they are answered with `200` and open nothing. ## Troubleshooting - **`401 INVALID_KEY`**: the URL's key is wrong or the integration is disabled. Copy the **Webhook URL** again from the integration. - **Alerts are titled "KOI alert"**: KOI sent the alert name under a field EvoHub does not know yet. The alert's labels show every field that arrived; send us the label names, or a sample delivery, so the field can be mapped. - **Nothing arrives**: check that the webhook in KOI is enabled and selects the alerts you expect. ## Related - [Integrations overview](https://docs.evohub.io/integrations-overview.md) - [Cortex XDR](https://docs.evohub.io/cortex-xdr.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/status-pages-overview.md # Status Pages A status page tells your customers whether your services are working, what is wrong when they are not, and what work is planned. This page walks through creating one, choosing its layout and branding, and putting it live. ## How a status page fits together A status page is made of a few parts, each covered on its own page: | Part | What it is | |------|-----------| | [Components](https://docs.evohub.io/components-and-groups.md) | The services visitors see a status for, such as "API" or "Dashboard". A component's status is set by hand or follows an uptime monitor. | | [Component groups](https://docs.evohub.io/components-and-groups.md#component-groups) | Optional groups of components, one per product, region or service. | | [Incidents and maintenance](https://docs.evohub.io/status-incidents-and-maintenance.md) | Published from On-Call and shown on the page with their updates. | | [Subscribers](https://docs.evohub.io/subscribers.md) | Visitors who follow the page by email or through an RSS/Atom feed. | | [Custom domain](https://docs.evohub.io/status-page-custom-domain.md) | The address the page is served on, such as `status.example.com`. | Status pages live under **Status Pages** in the EvoHub console. **All status pages** lists every page in your organization. If you pick a team in the team switcher, the list shows that team's pages. ## Create a status page :::steps ### Open Status Pages In the EvoHub console, open **Status Pages** from the **Services** menu. ### Start a new page Click **New status page** (or **Create your status page** if you have none yet). ### Name it and add a logo Enter a **Name**, for example `Acme Status`. You can upload a logo now (PNG, JPEG or WebP, up to 2 MB) or paste a logo URL. Both are optional and can be changed later. ### Create Click **Create Page**. EvoHub opens the new page's components. ::: A page created while a team is selected in the team switcher belongs to that team. ## Inside a status page An open status page has its own menu: | Item | What you do there | |-----|------------------| | **Components** | Add components, group them, set their status and pick the public layout. | | **Incidents** | See the incidents and scheduled maintenance published to this page. | | **Subscribers** | Turn email subscriptions on or off and see who is subscribed. | | **Settings** | The page's settings, in tabs: **General**, **Brand**, **Domain**, **Analytics** and **Links**. | **View public page** in the header opens the live page. The page's numbers — views, visitors, feed reads, AI readers and subscriber growth — are in [EvoTrail](https://docs.evohub.io/evotrail-overview.md), under **Status Pages**. ## Choose a public layout Once a page has components, the **Components** tab shows **Public page layout**. It decides how visitors see your components: | Layout | What visitors see | |--------|------------------| | **One list** (default) | Every component in one plain list. Group names are not shown. | | **Folded groups** | Each group folds into one line with its worst status and one uptime bar. Visitors can open a group to see its components. | | **A page per group** | The main page shows a card for each group, and each group has its own page at an address made from its name, such as `status.example.com/payments`. | The two grouped layouts need at least one group. See [Components and groups](https://docs.evohub.io/components-and-groups.md) for how to create groups, choose which groups start open, and the limit on **A page per group**. ## Settings Each tab saves separately: a tab with fields to fill in has its own **Save changes** button, and uploads and the domain take effect straight away. Above the tabs, the publish banner shows whether the page is **Published** and where it is live (see [Publish the page](#publish-the-page)). ### General - **Name**: the title of the public page, its browser tab and your console. - **Customization**: the page's text and links. | Field | What it does | |-------|-------------| | **Page description** | Shown under the header on the public page. | | **Footer text** | Small print at the bottom of the page, for example `© 2026 Acme`. | | **Support URL** | Where the support link in the header points. Use a `mailto:` or `https:` address. The link only appears when this is set. | | **Support button label** | The text of that link. Defaults to **Report a problem**. | | **Privacy policy URL** / **Terms of service URL** | Linked in the footer. | | **History window** | How far back the uptime bars and the incident calendar go: **30 days** or **90 days** (the default). | | **Show this page in search engine results** | Clear it to ask search engines not to index the page. | | **Show uptime percentage** | The "99.98% uptime" figure next to each component's bars. Clear it to keep the bars without the figure. On by default. | - **Danger zone**: delete the page (see [Delete a status page](#delete-a-status-page)). ### Brand - **Logo**: shown in the page header. PNG, JPEG or WebP, up to 2 MB. **Show the page name next to the logo** decides whether the header shows the name beside it or the logo alone. - **Favicon**: the browser-tab icon. If you do not set one, the logo is used. - **Brand colour** and the **Public page theme** (**Light** or **Dark**), or, on pages that use the shared theme editor, the colour scheme, accent colour, corner radius and fonts, with a live **Preview**. Nothing is live until you save. ### Domain Connect your own domain. See [Custom domain](https://docs.evohub.io/status-page-custom-domain.md). ### Analytics - **Count how the status page is read**: page views, feed reads and AI crawlers, counted at the edge with no cookies, no script in the visitor's browser and no stored IP address. A hash that changes daily tells visitors apart, so visitors are counted per day. Visitors who send Do Not Track or Global Privacy Control are not counted. - **Keep the counts for**: 30, 90 or 180 days, 1 year or 2 years. - **Google Analytics measurement ID**: a GA4 ID such as `G-XXXXXXX` sends page views to your own Google Analytics property. With **Respect Do Not Track and Global Privacy Control** on, Google Analytics is not loaded for visitors who send either signal. The counts themselves are in EvoTrail. ### Links **Header links** puts up to 5 links in the public page's header (in the menu on phones) and footer — your docs, your changelog or your product. Each link has a label and an `https://` address. **Add link** adds one; if you have EvoHub docs sites or changelogs, they are offered with one click. Every public page also has a small "Powered by EvoHub" link in the footer. ## Publish the page A status page goes live on your own domain. Until you connect one, **View public page** is disabled. :::steps ### Add components On the **Components** tab, add the services you want to show. See [Components and groups](https://docs.evohub.io/components-and-groups.md). ### Connect a domain In **Settings → Domain**, connect a subdomain such as `status.example.com` and add the DNS records the console shows. See [Custom domain](https://docs.evohub.io/status-page-custom-domain.md). ### Check it When the domain shows **Verified · SSL active**, click **View public page**. ::: ### Before you connect a domain A custom domain is required to publish: there is no EvoHub address or preview link that shows the public page before one is connected, and **View public page** stays disabled until then. What you can check in the console first: - the components and groups on the **Components** tab, with small sketches of each layout under **Public page layout**, - the theme's **Preview** on the **Brand** tab (on pages that use the theme editor). Once the domain is verified, the page is public. To keep search engines away while you look it over, clear **Show this page in search engine results** in **Settings → General** before you connect the domain. Changes you make in the console, and incidents published from On-Call, reach visitors within about 30 seconds. An open public page refreshes itself, so visitors do not need to reload. ### Hide a page without deleting it In **Settings**, select **Take offline** on the publish banner. Visitors to your domain then see "This status page is not configured", and the page's feeds stop working. Select **Publish status page** to restore it. ## Delete a status page In **Settings → General**, under **Danger zone**, click **Delete status page**. Type the page's name to confirm, then click **Delete status page** again. > [!WARNING] > Deleting a page permanently removes its public page, components, incidents, maintenance windows and connected custom domain. It cannot be undone. ## Who can do what Status page actions follow your organization role. The permissions involved are: | Action | Permission | |--------|-----------| | Create, edit, brand, publish or delete a page; connect a domain | `status:page:write` | | Add, edit, reorder and group components; set a component's status | `status:component:write` | | See incidents / maintenance on the page | `status:incident:read` / `status:maintenance:read` | | See subscribers / remove subscribers and change subscription settings | `status:subscriber:read` / `status:subscriber:write` | The built-in Owner, Admin and Member roles hold all of these. Viewers can see pages but not change them. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). ## Billing Status pages are billed by usage. See [How billing works](https://docs.evohub.io/how-billing-works.md) for what is metered and the current rates. ## Related - [Components and groups](https://docs.evohub.io/components-and-groups.md) - [Incidents and maintenance](https://docs.evohub.io/status-incidents-and-maintenance.md) - [Custom domain](https://docs.evohub.io/status-page-custom-domain.md) - [Subscribers](https://docs.evohub.io/subscribers.md) - [Uptime bars and percentage](https://docs.evohub.io/uptime-history.md) --- Source: https://docs.evohub.io/components-and-groups.md # Components and groups Components are the services your visitors see a status for. This page covers adding them, setting their status, linking them to uptime monitors, grouping them, and how the uptime history on the public page is built. ## Add a component :::steps ### Open the Components tab Open your status page in **Status Pages** and select **Components** in its menu. ### Add it Click **Add component** (or **Add your first component**). ### Fill in the details - **Name**: what visitors see, for example `API`. - **Description** (optional): what the component covers. - **Group** (optional): the product it belongs to. See [Component groups](#component-groups). - **Status source**: **Set manually** or **Sync from monitor**. See [Status source](#status-source). - **Initial status**: the status a manual component starts with. ### Save Click **Add Component**. ::: To change a component later, use the pencil icon on its row. To remove it, use the trash icon and confirm. ## Component statuses A component is always in one of five statuses: | Status | Use it when | |--------|------------| | **Operational** | The service works normally. | | **Degraded Performance** | It works, but slowly or with errors for some requests. | | **Partial Outage** | Part of the service is down, or it is down for some customers. | | **Major Outage** | The service is down. | | **Maintenance** | Planned work is in progress. | The page's overall banner at the top shows the worst status across its components, for example **All Systems Operational** or **Major Outage**. ## Status source ### Set manually A manual component keeps the status you give it. Change it from the status control on the component's row in the **Components** tab. ### Sync from monitor A synced component follows an [uptime monitor](https://docs.evohub.io/uptime-overview.md). Pick the monitor under **Uptime Monitor** when you add or edit the component. Its row then shows a **Synced** badge, and its status cannot be set by hand. The status follows the monitor: | Monitor state | Component status | |---------------|-----------------| | Up | Operational | | Degraded (slow) | Degraded Performance | | Down | Major Outage | | Paused | Maintenance | If EvoHub cannot read the monitor's state for a moment, the page keeps showing the component's last stored status. ### What visitors see during incidents and maintenance A published incident or a maintenance window in progress can show a component differently from its own status: - An active incident colors its affected components by its public impact: **Minor** shows as Degraded Performance, **Major** as Partial Outage, **Critical** as Major Outage. A component already in a worse state keeps the worse one. - A maintenance window in progress shows its affected components as **Maintenance**, even if their monitor reports them down. When that happens, the component's row in the console says so, for example "Major Outage on the page · API latency", so you see what visitors see. The status control on the row still shows the component's own status. See [Incidents and maintenance](https://docs.evohub.io/status-incidents-and-maintenance.md). ## Order components Visitors see components in the order the console shows them. To reorder, drag a component by its handle onto another, or use the up and down arrows beside it. Dropping a component onto one in another group also moves it into that group. ## Component groups A group is a product: a name and the components in it. Groups only change what visitors see when the page uses the **Folded groups** or **A page per group** layout. With **One list**, groups help you organize the console but are not shown publicly. ### Create a group You can create a group in two ways: - Type a new name in a component's **Group** field. The field suggests the page's existing groups as you type. - Click **New group**, give it a **Name**, tick the components that belong in it, and click **Create group**. Components you pick move out of any group they were in. Group names are unique on a page. ### Manage groups Each group has controls in its heading: - **Add**: add a component straight into this group. - **Rename group** (pencil icon): renames the group everywhere, including its subscribers and its logo. Clear the name to ungroup its components. - **Move group up** / **Move group down**: change the order of groups. - **Starts open** (Folded groups layout only): whether the group is expanded when a visitor opens the page. Groups are folded by default. ### Groups as product pages With **A page per group**, the main page shows a card per group, and each group gets its own page. The group heading in the console shows that page's address, for example `/payments`, and how many subscribers follow it. - **Group logo**: click the image slot beside the group name to upload a logo for its card and page (PNG, JPEG or WebP, up to 2 MB). Use the **×** to remove it. - **Limit**: **A page per group** allows up to 3 groups. The console will not let you add a fourth group while this layout is on. A page that already has more groups cannot switch to it until you merge or ungroup some. - **Ungrouped components**: components in no group are not shown to visitors in this layout. The console warns you about them. Move them into a group, or give that set a name. ## Uptime history The public page shows a row of daily bars for each component, covering the page's **History window** (30 or 90 days, set in **Settings → General**), with the uptime percentage for that period. - An incident that affected the component colours the days it was open, by its public impact; maintenance colours its days blue. - For a component synced from a monitor, other days follow the monitor's day. For a manual component, they are green. - Today's bar shows a component that is not operational right now. Hover over a day to see what happened. In the **Folded groups** layout, each group shows one combined bar; open the group to see each component's bar. How each day and the percentage are worked out is on [Uptime bars and percentage](https://docs.evohub.io/uptime-history.md). ## Related - [Status Pages](https://docs.evohub.io/status-pages-overview.md) - [Incidents and maintenance](https://docs.evohub.io/status-incidents-and-maintenance.md) - [Uptime monitoring](https://docs.evohub.io/uptime-overview.md) - [Uptime bars and percentage](https://docs.evohub.io/uptime-history.md) --- Source: https://docs.evohub.io/status-incidents-and-maintenance.md # Status page incidents and maintenance Incidents and scheduled maintenance reach your status page from On-Call. You run the incident or the maintenance window in On-Call, and the status page shows it to visitors and keeps it in step. This page explains what gets published, how it stays up to date, and what visitors see. > [!NOTE] > The **Incidents** tab of a status page is read-only. It lists what On-Call has published to the page. To publish, update or remove anything, use On-Call. ## Publish an incident :::steps ### Open the incident in On-Call Go to **On-Call → Incidents** and open the incident. ### Publish it Click **Publish to status page**. ### Fill in what visitors see - **Status page**: which page to publish to. - **Public impact**: **None**, **Minor**, **Major** or **Critical**. It starts from the incident's severity; change it to what customers actually experience. - **Affected components**: tick the components the incident affects. - **Public message**: the first update visitors read. If you leave it empty, EvoHub uses the incident's summary, or "We are investigating an issue." when there is none. ### Publish Click **Publish**. The incident header then shows an **On status page** badge. ::: The public impact decides how the affected components look while the incident is open: **Minor** shows them as Degraded Performance, **Major** as Partial Outage, and **Critical** as Major Outage. **None** shows the incident without changing any component's color. Subscribers who follow the affected components, or the whole page, get an email. See [Subscribers](https://docs.evohub.io/subscribers.md). ## Keep a published incident up to date Once an incident is published, On-Call keeps the status page in step: | You do this in On-Call | The status page shows | |------------------------|----------------------| | Click **Mark Identified** | A new update, "Issue identified.", with the status Identified. | | Add a timeline note | Your note as a new update, at the incident's current status. | | Edit or delete a timeline note | The same change on the published update. | | Change the incident's title | The new title. | | Change the incident's severity | A public impact derived from the new severity. | | Click **Resolve** | A final update, "Issue resolved.", and the incident moves to the page's history. | Each update is emailed to the incident's subscribers. To take an incident off the page, click **Unpublish** next to the **On status page** badge and confirm. ## Publish a postmortem After an incident is resolved, you can write a postmortem in On-Call with the **Postmortem** button on the incident. **Save internal** keeps it private. **Publish publicly** shows it under the incident on your status page. You can unpublish it later. The status page's **Incidents** tab shows a **Postmortem** badge on incidents that have one. ## Record a past incident You can publish an incident that has already been resolved, for example one you handled before you had a status page. Use **Publish to status page** on the resolved incident as usual. A past incident: - appears on the days it actually happened, in the uptime bars and the incident calendar - is marked **Past** in the status page's **Incidents** tab - does not email any subscriber ## Schedule maintenance :::steps ### Open Maintenance in On-Call Go to **On-Call → Maintenance** and click **New maintenance**. ### Describe the work Enter a **Title** and a **Description**, and choose the **Start** and **End** times. ### Decide what it affects - **Silence on-call alerts for this window**: mute On-Call paging and Uptime alerts for the whole organization while the work runs. Unticked, the window is shown but silences nothing. - **Status page (optional)**: the page to publish the window on. - **Affected components**: the components the work touches, once a page is chosen. - **Silence uptime monitors (optional)**: monitors whose alerts should be muted during the window. ### Schedule Click **Schedule**. ::: When a window with a description is published to a status page, its subscribers get an email. ### What visitors see - **Before it starts**: a "Scheduled maintenance" notice near the top of the page when the start is within the next 7 days. Clicking it opens the window's page. The window also appears in the incident calendar. - **While it runs**: the affected components show **Maintenance**, and the window is listed at the top of the page. This happens automatically between the start and end times. - **After it ends**: it moves to the page's history. ### Update or finish maintenance Open the window under **On-Call → Maintenance**: - **Post update** adds a timeline update. It appears on the status page and is emailed to subscribers. - **Edit** changes the title, description or schedule, for example to extend a window that is running long. - **End now** (while it runs) or **Mark completed** finishes it, and asks when the work finished: - **Now** (the default while it runs), or **At the scheduled end** (the default once the scheduled end has passed). - **At another time**, for work that finished earlier or ran over. The time must be between the window's start and now. The status page adds a closing update, "This maintenance has been completed.", dated at that time. The window's page shows the time it ended next to its schedule, and the uptime bars and percentage count the maintenance only up to that end. Use this when you forgot to close a window: choose the time it really ended, not the time you noticed. ## What visitors see The public page shows incidents and maintenance in a few places: - **The banner and the top of the page**: the overall status, and anything happening now listed by name. - **Components**: affected components take the incident's or maintenance's color, as described above. - **Incident & Maintenance Calendar**: a month-by-month calendar of incidents and maintenance across the page's history window, with each one's updates and, if published, its postmortem. - **Permalinks**: every incident and maintenance window has its own address, `/incidents/` or `/maintenances/` on your status page domain. Emails to subscribers link there. On a page that uses **A page per group**, each group's page shows only the incidents and maintenance that affect that group's components. ## Related - [Alerts and incidents in On-Call](https://docs.evohub.io/alerts-and-incidents.md) - [Components and groups](https://docs.evohub.io/components-and-groups.md) - [Subscribers](https://docs.evohub.io/subscribers.md) - [Silence windows](https://docs.evohub.io/uptime-alerts-and-silence-windows.md#silence-windows) --- Source: https://docs.evohub.io/subscribers.md # Status page subscribers Visitors can follow your status page in two ways: by email, or with a feed reader using the page's RSS or Atom feed. This page covers turning subscriptions on, what subscribers receive, and how to manage them. ## Turn on email subscriptions :::steps ### Open the Subscribers tab Open your status page in **Status Pages** and select **Subscribers** in its menu. ### Enable subscriptions Tick **Enable email subscriptions on the public page**. ### Save Click **Save changes**. ::: The public page then shows a **Subscribe** button in its header. Turn the setting off to hide the button and stop sending update emails. ## How visitors subscribe :::steps ### Open the form The visitor clicks **Subscribe** in the page header. ### Choose what to follow On a page that uses the **A page per group** layout, the main page lets them pick **Everything on this page** or one or more products. On a product's own page, they follow that product. On other layouts they follow the whole page. ### Enter an email address They enter their address and click **Subscribe**. ### Confirm EvoHub sends a confirmation email, "Confirm your subscription to *your page name*". The subscription starts only after they click the link in it. ::: Following a product covers every component in that group, including components you add to it later. A visitor who subscribes again with the same address keeps their existing subscription and adds what they picked the second time. The form has built-in protection against bots, and repeated sign-ups for the same address are limited, so a confirmation email is not re-sent on every click. ## What subscribers receive Confirmed subscribers get an email when: - an incident affecting what they follow is published to the page - an update is posted to that incident - maintenance affecting what they follow is scheduled with a description - an update is posted to that maintenance Subscribers who follow the whole page get every one of these. The subject reads like `[Acme Status] API latency — Investigating`, and the email links to the incident's or maintenance's own page on your status page. Past incidents, published after they ended, are not emailed. See [Incidents and maintenance](https://docs.evohub.io/status-incidents-and-maintenance.md#record-a-past-incident). Emails are sent from `no-reply@status.evohub.io` with your page's name as the sender name, for example "Acme Status (via EvoHub)". Replies are not read. Subscribers who need to reach you can use the support link in your page header; set it with **Support URL** in **Settings → General**. ### Unsubscribe Every email has an **Unsubscribe** link. One click removes the subscription. ## Manage subscribers The **Subscribers** tab lists everyone who has signed up, with: - their email address - a **pending** or **confirmed** badge (pending means they have not clicked the confirmation link yet) - what they follow: **Everything**, a group (shown as "*name* (group)"), or specific components - when they signed up To remove a subscriber, click the trash icon on their row. Seeing subscribers needs `status:subscriber:read`; removing them or changing the setting needs `status:subscriber:write`. Owners, Admins and Members have both. ## RSS and Atom feeds Every public status page has two feeds on its own domain: ```text https://status.example.com/feed.rss https://status.example.com/feed.atom ``` Visitors also find links to them at the bottom of the **Subscribe** form. Each feed item is one update to an incident or a maintenance window, newest first, up to the 50 most recent. Feeds need no sign-up, and they stop working if you take the page offline with **Take offline** in **Settings**. ## Related - [Incidents and maintenance](https://docs.evohub.io/status-incidents-and-maintenance.md) - [Components and groups](https://docs.evohub.io/components-and-groups.md) - [Custom domain](https://docs.evohub.io/status-page-custom-domain.md) --- Source: https://docs.evohub.io/status-page-custom-domain.md # Status page custom domain Your status page is served on a subdomain you own, such as `status.example.com`. You connect it in the console, add two DNS records at your DNS provider, and EvoHub issues and renews the TLS certificate for you. A status page goes live only once a domain is connected. ## Before you start - Use a **subdomain**, such as `status.example.com`. Apex domains such as `example.com` are not supported. - You need access to your domain's DNS settings to add CNAME records. - Each status page has one custom domain. ## Connect a domain :::steps ### Open the Domain tab Open your status page in **Status Pages** and go to **Settings → Domain**. ### Enter your domain Under **Custom domain**, type the subdomain in **Your domain**, for example `status.example.com`, and click **Connect domain**. ### Copy the DNS records The console shows the records to add, each with a **Copy** button: | Type | Name | Value | |------|------|-------| | `CNAME` | `_acme-challenge.status.example.com` | The validation value the console shows for your domain | | `CNAME` | `status.example.com` | `cname.evohub-dns.com` | ### Add the validation record first At your DNS provider, add the `_acme-challenge` CNAME exactly as the console shows it. This record lets EvoHub prove the domain is yours and issue its certificate. ### Then add the routing record Once validation succeeds, add the CNAME from your subdomain to `cname.evohub-dns.com`. Adding the records in this order means visitors are never sent to the domain before its certificate is ready. ### Wait for it to go green The status badge updates on its own while validation is in progress. You can also click **Re-check**. When it reads **Verified · SSL active**, click **View public page**. ::: > [!TIP] > If your domain's DNS is on Cloudflare with the proxy turned on, set Cloudflare's SSL/TLS mode to **Full**, not **Flexible**. ## Preview before connecting a domain You can see the status page exactly as readers will, before it has a domain or without one. Open the page in the console and, at the top, choose **Preview**. The status page opens in a new tab, drawn by the same renderer as the live site, with a thin banner on top: *Preview — only your team can see this. Connect a domain to publish.* - Only signed-in members of your organization who can read the status page can open a preview. The link from the button works once and only for two minutes; after that, choose **Preview** again. - A preview lasts 15 minutes from the moment you ask for it. Links inside it stay within the preview. - Previews are never indexed or cached, are not counted in analytics, and are not billed. - Nothing can be written from a preview: the subscribe form is hidden, so nobody is subscribed and no email is sent. Google Analytics does not run. ## Domain statuses | Badge | Meaning | |-------|---------| | **Pending DNS** | The domain is connected in EvoHub, but it has not been validated yet. Check the DNS records. | | **Validating** | The domain was accepted and the certificate is being validated and issued. | | **Verified · SSL active** | The domain is live with a valid certificate. | | **Action needed** | The domain was blocked or its certificate expired. Check that both records still match what the console shows and click **Re-check**. If it stays this way, remove the domain and connect it again, or contact info@evosync.io. | DNS changes can take a while to spread, depending on your provider and the records' TTL. The console keeps checking while a domain is **Pending DNS** or **Validating**. ## TLS certificates EvoHub issues the certificate for your domain automatically once validation succeeds, and renews it before it expires. Keep the `_acme-challenge` record in place so renewal keeps working. There is nothing to upload. ## Change or remove a domain To move the page to a different domain, remove the current one and connect the new one. To remove a domain, click **Remove domain** in **Settings → Domain** and confirm. The page stops being served on that address. You can then delete the two CNAME records at your DNS provider. Deleting a status page also removes its custom domain. ## Who can connect a domain Connecting, re-checking and removing a domain needs `status:page:write`, which Owners, Admins and Members have. ## Related - [Status Pages](https://docs.evohub.io/status-pages-overview.md) - [Subscribers](https://docs.evohub.io/subscribers.md) - [Docs custom domain](https://docs.evohub.io/docs-custom-domain.md) --- Source: https://docs.evohub.io/uptime-history.md # Uptime bars and percentage Every component on a public status page shows a row of daily bars and an uptime percentage. Both come from the same rule, day by day, so the percentage always agrees with the bars. This page explains that rule. ## The days The bars cover the page's **History window**: the last 30 or 90 days (90 by default), set in **Settings → General**. Each bar is one calendar day in UTC, and the last bar is today. ## What colours a day For each component and each day, the first of these that applies decides the bar: 1. **An incident that affected the component.** An incident published from On-Call colours every day it was open, from its start until it was resolved (or until now, if it is still open). The colour follows its **Public impact**: **Critical** is red, **Major** orange, **Minor** yellow, and **None** blue. When several incidents overlap a day, the most severe one colours it. 2. **Scheduled maintenance that affected the component.** The day is blue. A window that was completed counts up to the time it ended, not its scheduled end. 3. **The uptime monitor's day**, for a component synced from a monitor: green when the monitor rated the day operational, yellow when degraded (Degraded Performance), red when down (Major Outage). 4. Otherwise the day is green. Today's bar also shows a component that is not operational right now: if nothing above has coloured today, it takes the component's current status. Hover over a bar to see what coloured it: the incident's or maintenance's title, or the status. ## How the percentage is worked out Each day gets an uptime between 0% and 100%, and the percentage shown is the average of those days. | Day | Its uptime | | --- | --- | | A day with an incident whose impact is **Minor**, **Major** or **Critical** | The share of the day the incidents did not cover. A 6-hour incident makes that day 75%. Overlapping incidents are counted once, and today counts only up to now. If the component follows a monitor, the day is never higher than the monitor measured. | | A day with only an impact **None** notice, or with maintenance | 100%. Planned work and notices are not downtime. | | Any other day, for a component synced from a monitor | The monitor's measured uptime for that day. | | Any other day, for a component with no monitor | 100%. | | A day before the monitor had any data | Skipped: it does not count either way. | The impact matters, not the incident's severity in On-Call: a **Minor** incident costs as much time as a **Critical** one. To show an incident without counting it as downtime, publish it with impact **None**. See [Incidents and maintenance](https://docs.evohub.io/status-incidents-and-maintenance.md). A manual component that is down right now with no incident open counts today as 0%. A component with nothing to count at all reads 100.00%. > [!NOTE] > A monitor reading caps a day; it never raises it. If the monitor saw the component up while an incident was open, the incident still costs its share of the day. ## Hiding the percentage To show the bars without the percentage, untick **Show uptime percentage** in the status page's **Customization** settings and save. The bars, their colours and tooltips stay; only the "99.98% uptime" figures are left out, on component rows, groups, the history section and product cards. It is on for every page until you turn it off. ## Groups In the **Folded groups** layout, a group's row shows one combined bar per day and one percentage: - The combined bar is blue when the only thing affecting its components that day is maintenance or impact **None**. Otherwise its colour follows how many of the group's components were affected and how badly, and the tooltip says, for example, "2 of 5 components". - The group's percentage is the average of its components' percentages. ## Related - [Components and groups](https://docs.evohub.io/components-and-groups.md) - [Incidents and maintenance](https://docs.evohub.io/status-incidents-and-maintenance.md) - [Uptime overview](https://docs.evohub.io/uptime-overview.md) --- Source: https://docs.evohub.io/uptime-overview.md # Uptime monitoring EvoHub Uptime checks your services on a schedule and tells you when one stops answering, answers wrongly or slows down. This page covers the monitor types, how to create a monitor, and the settings that decide when a monitor counts as down. ## How it works A monitor checks one target at a fixed interval. When a check fails, the monitor goes **Down**, EvoHub opens an uptime incident, and it alerts you through the monitor's [notification channels](https://docs.evohub.io/uptime-alerts-and-silence-windows.md) and, if you choose, pages your team through On-Call. When a later check passes, the incident closes and EvoHub sends a recovery message. Monitors live under **Uptime** in the EvoHub console. If you pick a team in the team switcher, the **Monitors** list shows that team's monitors, and new monitors belong to that team. ## Monitor types | Type | Console label | What it checks | Target | |------|---------------|----------------|--------| | HTTP | **HTTP** | Requests a URL and checks the response code, and optionally the response body, headers and SSL certificate. | A full URL, such as `https://example.com/health` | | Keyword | **Keyword** | Requests a URL and checks that the response body contains a word or phrase. | A full URL | | TCP | **TCP** | Opens a TCP connection to a port. | `host:port`, such as `db.example.com:5432` | | Ping | **ICMP (Ping)** | Sends a ping to a host. | A host name or IP address | | Heartbeat | **Heartbeat (cron / job)** | Waits for your job to ping EvoHub, and alerts when a ping is late. | None. EvoHub gives you a URL to call. | Heartbeat monitors work the other way round from the rest. See [Heartbeat monitors](https://docs.evohub.io/heartbeat-monitors.md). ## Create a monitor :::steps ### Open Uptime In the EvoHub console, open **Uptime** and click **New Monitor**. ### Name it and pick a type Enter a **Name** and choose the **Monitor Type**. The fields below change to match the type. ### Enter the target Fill in **URL**, **Host and port** or **Host**, depending on the type. ### Set the schedule Choose a **Check Interval** and a **Timeout**. ### Set the options you need Open the sections below the form to add HTTP settings, SSL monitoring, assertions, performance and SLA settings, tags, and alerting. All of them are optional. ### Create Click **Create Monitor**. EvoHub opens the monitor's page and starts checking. ::: ## Check interval and timeout | Setting | Options | Default | |---------|---------|---------| | **Check Interval** | 15 seconds, 30 seconds, 1 minute, 5 minutes, 15 minutes, 30 minutes, 60 minutes | 5 minutes | | **Timeout** | 10 seconds, 30 seconds, 60 seconds | 30 seconds | A check that gets no answer within the timeout fails. A monitor goes down on its first failed check, so you hear about an outage on the next check after it starts. EvoHub does not currently let you choose the regions checks run from. Each check's location is shown in the monitor's **Recent Checks** table. ## HTTP and keyword options **Expected Status Code** (HTTP monitors, default `200`): the response code that counts as up. **Keyword** (keyword monitors): the text the response body must contain. The match is case-sensitive. **HTTP Settings** (HTTP monitors): - **Method**: `GET`, `POST`, `HEAD` or `PUT`. - **Headers**: request headers to send, for example an API token your health endpoint needs. - **Request Body**: sent with `POST` and `PUT`. - **Basic Auth**: a username and password. Credentials you enter are write-only. Once saved, a header's value and the Basic Auth password are never shown again, in the console or through the API: the field says **Set — leave blank to keep**. Leave it blank to keep the stored value, or type a new one to replace it. Credentials inside the monitor's URL, such as `https://user:pass@…` or a `token`, `key`, `secret`, `password` or `api_key` query parameter, are shown masked as `****`; checks still use the URL as you entered it. ## Assertions HTTP monitors can check more than the status code. Open **Assertions** and click **Add assertion**. Every assertion must pass, or the check fails and the reason is recorded, for example "Expected status 200–299, got 503". | Assertion | Operators | Example | |-----------|-----------|---------| | **Status code** | is one of | `200-299,301` | | **Body** | contains, does not contain | `ok` | | **JSON value** | equals, does not equal, contains, exists | field `$.status` equals `up` | | **Header** | equals, contains | field `Content-Type` contains `application/json` | - A monitor can have up to 10 assertions. - JSON paths look like `$.status` or `$.items[0].id`. - Without a status code assertion, **Expected Status Code** still applies. - Up to 1 MB of the response body is read. ## Slow responses and SLA Open **Performance & SLA** on any monitor except a heartbeat: - **Slow if response takes longer than** (milliseconds, 1 to 60000): a passing check slower than this marks the monitor **Degraded** instead of Up. Leave it empty to turn it off. Degraded counts as up for uptime and SLA. - **Also page on-call when slow**: raises a lower-priority On-Call alert while the monitor is degraded, and resolves it when the monitor recovers. It needs a slow threshold and an escalation policy under **Alerting**. - **SLA target**: an uptime percentage such as `99.9`. When 30-day uptime falls below it, the monitor is marked as breaching its SLA. ## SSL certificate expiry HTTP monitors can watch the site's certificate. Open **SSL Monitoring**, turn on **Monitor SSL certificate**, and pick an **Alert threshold**: 7, 14, 30 (the default) or 60 days. When the certificate has fewer days left than the threshold, EvoHub sends a warning to the monitor's notification channels, for example "SSL certificate expires in 12 days (threshold: 30 days)". The warning is only sent while the monitor is up. The monitor's page shows the days left, and the **Monitors** list flags certificates with less than 30 days left. ## Tags Use **Tags** to group monitors, for example by service or environment. Press Enter or a comma to add a tag. The **Monitors** list can be filtered by tag. ## Alerting Under **Alerting**, pick an **Escalation Policy** to page your team through On-Call when the monitor goes down, and choose **Re-alert while still down**: every 5, 15 (the default), 30 or 60 minutes. See [Uptime alerts and silence windows](https://docs.evohub.io/uptime-alerts-and-silence-windows.md). ## The monitor page Open a monitor from the list to see: - its current status and URL, and the reason for the last failure - days left on the SSL certificate, if monitored - **Longer history and SLA in EvoTrail**, which opens EvoTrail's Uptime tab narrowed to this monitor: uptime over time, its SLA, response times and downtime (see [EvoTrail](https://docs.evohub.io/evotrail-overview.md)) - **Recent Checks**: status, location, latency, HTTP code, time and error of each check - **Incidents**: when each outage started and ended, how long it lasted, and its cause - **Notification Channels** linked to this monitor From the header you can **Pause** and **Resume** checking, **Edit** the monitor, or delete it. A paused monitor runs no checks. Individual check results are kept for 90 days. ### Monitor statuses | Status | Meaning | |--------|---------| | Up | The last check passed. | | Degraded | Checks pass, but slower than the slow threshold. | | Down | The last check failed. | | Unknown | The monitor has not been checked yet, or the last result was inconclusive. | The list also marks monitors that are **Paused**, or muted by a silence window (**Maintenance**). ## Show a monitor on a status page A status page component can follow a monitor, so your public page updates on its own when the monitor goes down. See [Components and groups](https://docs.evohub.io/components-and-groups.md#sync-from-monitor). ## Permissions and billing Creating, editing, pausing and deleting monitors needs `uptime:monitor:write`. Owners, Admins and Members have it; Viewers can see monitors but not change them. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). Uptime checks are billed by usage. A heartbeat monitor counts as one check each time it goes down or comes back, not per ping. See [How billing works](https://docs.evohub.io/how-billing-works.md). ## Related - [Heartbeat monitors](https://docs.evohub.io/heartbeat-monitors.md) - [Uptime alerts and silence windows](https://docs.evohub.io/uptime-alerts-and-silence-windows.md) - [Weekly email summary](https://docs.evohub.io/weekly-summary.md) --- Source: https://docs.evohub.io/heartbeat-monitors.md # Heartbeat monitors A heartbeat monitor watches something that runs on a schedule, such as a cron job, a nightly backup or a queue worker. Instead of EvoHub checking a target, your job calls a unique URL each time it runs. If a call does not arrive in time, EvoHub marks the monitor down and alerts you, the way it would for any other monitor. ## When to use one Use a heartbeat monitor when the thing you care about has no address to check, or when "it ran" is what matters: - scheduled jobs and cron tasks - backups and data exports - background workers that should check in regularly - anything that fails silently by simply not running ## Create a heartbeat monitor :::steps ### Start a new monitor In **Uptime**, click **New Monitor**, enter a **Name**, and set **Monitor Type** to **Heartbeat (cron / job)**. ### Set the expected ping interval Under **Expected ping interval**, enter how often your job runs, as a number and a unit: minutes, hours or days. For example, `1 days` for a nightly job or `3 hours` for one that runs every three hours. ### Set the grace period Under **Grace period**, enter how late a ping may be before the monitor goes down, in minutes or hours. Allow for how long your job takes and how much its start time varies. ### Choose alerting Optionally pick an **Escalation Policy** under **Alerting** to page your team through On-Call. You can also add **Tags**. ### Create Click **Create Monitor**. The monitor's page shows its ping URL. ::: ## Ping the URL The monitor's page has a **Heartbeat ping URL** card with the address to call and a **Copy** button. The URL looks like `https://evohub.io/ping/`; always copy the exact one from the console. Call it at the end of your job, after the work has succeeded. `GET`, `POST` and `HEAD` all work, and no API key or other credential is needed. :::code-group ```bash [cron] # Runs at 02:00 every day, and pings only if the backup succeeds 0 2 * * * /usr/local/bin/backup.sh && curl -fsS https://evohub.io/ping/ ``` ```bash [shell script] #!/usr/bin/env bash set -euo pipefail ./run-export.sh curl -fsS --retry 3 https://evohub.io/ping/ ``` ::: A successful ping returns `200` with `{"status":"ok"}`. An unknown URL returns `404`. The card also shows **Last ping**, so you can confirm your job is reaching EvoHub. > [!WARNING] > The ping URL is the only credential. Anyone who has it can reset the monitor's timer. Keep it out of public repositories and logs. ## When a heartbeat goes down The monitor is down when the time since the last ping is longer than the expected interval plus the grace period. For example, with an interval of 1 day and a grace period of 30 minutes, a job that last pinged at 02:05 on Monday is marked down if no ping arrives by 02:35 on Tuesday. - A new monitor that has never been pinged gets one full interval plus grace, counted from when you created it, before it can go down. - When the monitor goes down, EvoHub opens an incident with the reason, for example "no heartbeat received for 25h0m0s", and alerts your notification channels and On-Call, just like a failed HTTP check. - While it stays down, you are alerted again on the monitor's **Re-alert while still down** schedule. - The next ping brings the monitor back up and closes the incident. ## How heartbeats differ from other monitors - There is no target, timeout, assertion, slow threshold or SLA target. - The monitor page records state changes rather than every ping, so **Recent Checks** shows when it went down and came back. - A heartbeat monitor is billed like an uptime check (0.001 EHU per check), but only when it changes state: one check for its first evaluation, then one each time it goes down or comes back. The pings your job sends are not counted. ## Pause or silence a heartbeat **Pause** on the monitor's page stops it from being evaluated, for example while a job is switched off. To mute alerts for a planned window instead, use a silence window. See [Uptime alerts and silence windows](https://docs.evohub.io/uptime-alerts-and-silence-windows.md#silence-windows). ## Related - [Uptime monitoring](https://docs.evohub.io/uptime-overview.md) - [Uptime alerts and silence windows](https://docs.evohub.io/uptime-alerts-and-silence-windows.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) --- Source: https://docs.evohub.io/uptime-alerts-and-silence-windows.md # Uptime alerts and silence windows When a monitor goes down, EvoHub can tell you in two ways: through **notification channels** (a Slack or Microsoft Teams channel, or a webhook) and by **paging On-Call** through an escalation policy. You can use either or both. Silence windows mute those alerts while you do planned work. ## What triggers an alert | Event | Notification channels | On-Call | |-------|----------------------|---------| | Monitor goes down | Down alert with the reason | Alert raised (if an escalation policy is set) | | Still down after the re-alert interval | Repeated down alert | Alerted again | | Monitor recovers | Recovery message with how long it was down | Alert resolved | | SSL certificate below its threshold | Warning with the days left | No | | Monitor is slow (degraded) | No | Lower-priority alert, if **Also page on-call when slow** is on | The reason in each alert is the failing check's error, for example "Expected status 200–299, got 503" or "keyword "ok" not found in response body". ## Notification channels A notification channel is a destination for uptime alerts. You create channels once, then link them to the monitors that should use them. ### Create a channel :::steps ### Open Notification channels In **Uptime**, select **Notification channels**. The **Where notifications go** card lists your channels. Click **Add destination**. ### Pick a type Choose **Slack or Microsoft Teams channel** or **Webhook**. ### Enter the destination - **Slack or Microsoft Teams channel**: pick a **Channel** from the ones connected in **Organization → Chat apps**. If none is connected yet, connect [Slack](https://docs.evohub.io/slack.md) or [Microsoft Teams](https://docs.evohub.io/microsoft-teams.md) there first. - **Webhook**: enter a **Name** and your endpoint's **Webhook URL** (it must start with `https://`). Optionally set a **Secret header** value so your endpoint can check that requests come from EvoHub. Private and local network addresses are not allowed. ### Save Click **Add destination**. ::: To delete a channel, click **Remove** next to it. Channels made earlier from a Slack incoming-webhook URL are marked **Legacy**. They keep working until you remove them. Once Slack is connected in **Chat apps**, use **Move to Slack app** on the legacy channel and pick the Slack channel. Then link the new channel to the monitors that used the old one, on each monitor's page, and remove the old one. EvoHub does not currently offer an email notification channel for Uptime. To be called, texted or emailed, page your team through On-Call instead. ### Link a channel to a monitor Open the monitor, find **Notification Channels**, and click **Add channel**. In **Link a notification channel**, pick one of your channels. A monitor only alerts the channels linked to it. To unlink one, use **Unlink channel** next to it. ### Webhook payload A webhook channel receives a `POST` with a JSON body. When you set a secret, it is sent in the `X-Webhook-Secret` header. Your endpoint should answer with a `2xx` status. :::tabs ::tab{title="Down"} ```json { "event": "triggered", "monitor_id": "mon_6f1c2e4a-0b7d-4d1e-9a52-2c8e7f3b1a90", "monitor_name": "Checkout API", "monitor_url": "https://api.example.com/health", "cause": "Expected status 200–299, got 503", "timestamp": "2026-10-09T14:30:00Z" } ``` ::tab{title="Recovered"} ```json { "event": "resolved", "monitor_id": "mon_6f1c2e4a-0b7d-4d1e-9a52-2c8e7f3b1a90", "monitor_name": "Checkout API", "monitor_url": "https://api.example.com/health", "timestamp": "2026-10-09T14:42:10Z", "down_duration_seconds": 730 } ``` ::: SSL expiry warnings are also sent with `"event": "triggered"`, with the warning as the `cause`. ## Page your team through On-Call To have a down monitor page whoever is on call, give it an escalation policy: :::steps ### Edit the monitor Open the monitor and click **Edit** (or set this when you create it). ### Pick an escalation policy Under **Alerting**, choose an **Escalation Policy**. **None — do not alert on-call** turns paging off. ### Choose how often to re-alert Set **Re-alert while still down**: every 5, 15, 30 or 60 minutes. ::: When the monitor goes down, On-Call receives a high-severity alert titled `Monitor Down: `, with the failure reason as its description. On-Call then notifies people as the [escalation policy](https://docs.evohub.io/escalation-policies.md) says. Repeats for the same outage are grouped into the same alert, and the alert resolves on its own when the monitor recovers. If you turned on **Also page on-call when slow**, a slow monitor raises a medium-severity alert titled ` is slow`, which resolves when the monitor is no longer degraded. Picking a policy needs permission to read On-Call escalation policies (`oncall:escalation:read`). Without it, the console explains that the monitor will not page anyone. ## Silence windows A silence window mutes alerts for a while, for example during a deployment or planned maintenance. Monitors keep checking while silenced. There are three ways to silence alerts. ### Silence the whole organization Use this to mute everything at once. :::steps ### Open the silence menu Click the bell icon, **Silence notifications**, in the top bar of the EvoHub console. ### Pick a duration Under **Silence uptime & on-call**, choose **30 min**, **1 hour**, **4 hours** or **8 hours**. ::: While the silence lasts, a banner reads "Notifications silenced — uptime & on-call alerts are muted", and the bell menu shows the time left. Uptime notification channels and On-Call paging are both muted for the whole organization. A silence always ends on its own; there is no open-ended option. To end it early, open the bell menu and click **Resume notifications**. Silencing needs `uptime:silence:write`, which Owners, Admins and Members have. People without it do not see the bell. ### Silence during a maintenance window When you schedule maintenance in **On-Call → Maintenance**, the **Schedule maintenance** form has two silencing options: - **Silence on-call alerts for this window**: while the window is in progress, On-Call does not page anyone, and Uptime does not open incidents or send alerts for any monitor in your organization. With the option unticked, the window still shows as maintenance but silences nothing. - **Silence uptime monitors (optional)**: pick monitors whose notification channels stay quiet between the window's start and end. These monitors show **Maintenance** in the **Monitors** list while muted. See [Incidents and maintenance](https://docs.evohub.io/status-incidents-and-maintenance.md#schedule-maintenance) for publishing the same window on a status page. ### Silence through the API You can also set silences with an [API key](https://docs.evohub.io/api-keys-and-scopes.md) that has the `uptime:silence:write` scope. Mute Uptime's notification channels across the organization for a number of minutes: ```bash curl -X PUT https://evohub.io/api/v1/silence \ -H "X-API-Key: $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"minutes": 60}' ``` `DELETE /api/v1/silence` lifts it, and `GET /api/v1/silence` returns `silenced_until`. This call mutes Uptime's notification channels only. The bell menu in the console also silences On-Call. Mute one monitor's notification channels for a fixed window: ```bash curl -X PUT https://evohub.io/api/v1/monitors//silence \ -H "X-API-Key: $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"starts_at": "2026-10-10T22:00:00Z", "ends_at": "2026-10-10T23:30:00Z"}' ``` `GET` on the same path returns the monitor's silence, and `DELETE` removes it. ## Related - [Uptime monitoring](https://docs.evohub.io/uptime-overview.md) - [Escalation policies](https://docs.evohub.io/escalation-policies.md) - [Notifications in On-Call](https://docs.evohub.io/notifications.md) - [Weekly email summary](https://docs.evohub.io/weekly-summary.md) --- Source: https://docs.evohub.io/weekly-summary.md # Weekly email summary The weekly email summary gives your organization's owners and administrators a short report every Monday: how your monitors did over the previous week. It is off until an Owner or Admin turns it on. ## What the email contains The summary covers the last full week, Monday to Sunday (UTC), across all of your organization's monitors: - **Average uptime** - **Incidents**: how many outages there were - **Total downtime** - **Avg response time** - the **Longest outage**, if there was one - a table of monitors with each one's uptime and downtime, the ones with the most downtime first - any monitor that is **Down right now**, called out at the top - a link to open the uptime dashboard An organization with no monitors gets no email. ## Who receives it The summary goes to the organization's **Owners and Admins**, because they can see every monitor, including team monitors. Members and Viewers do not receive it. It is sent every Monday from 09:00 UTC, by email only. It is not posted to Slack or webhook channels, and it is sent even while notifications are silenced, because silencing applies to alerts. ## Turn the summary on or off :::steps ### Open the setting In **Uptime**, select **Notification channels**. The **Weekly email summary** card is on that page. ### Switch it on Tick **Send the weekly summary for this organization**. Only Owners and Admins can change this; everyone else sees "Only owners and administrators can change this." ::: Clear the same box to stop the summary for everyone. ## Preview it Owners and Admins can click **Send me a preview** on the card to get last week's summary right away. Only you receive the preview; its subject starts with `[Preview]`. You can send one preview a minute. ## Stop receiving it yourself An Owner or Admin who does not want the email can opt out without turning it off for everyone else: - In the console, clear **Send it to me** on the **Weekly email summary** card. - From the email, click **Unsubscribe** at the bottom and confirm. To start receiving it again, tick **Send it to me**. ## Related - [Uptime monitoring](https://docs.evohub.io/uptime-overview.md) - [Uptime alerts and silence windows](https://docs.evohub.io/uptime-alerts-and-silence-windows.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) --- Source: https://docs.evohub.io/retro-boards.md # Retro boards A retro board is where your team looks back on a sprint, a release or an incident. People add cards to columns, vote and comment on them, and turn what they agree on into action items. This page covers creating a retro, running it, inviting guests, and closing it. Retro boards live under **Retro** in the EvoHub console. ## Create a retro board :::steps ### Start a new board In **Retro**, click **New Board**. ### Name it Enter a name, for example `Q2 Sprint 4 Retro`, and optionally a description. ### Choose who sees it - **Whole organization**: everyone in the organization. - ***Team name* (team only)**: that team and your administrators. You can share it with other teams afterwards. - **Only people I add**: nobody but you until you add people from the retro's **People** panel. Administrators always see it. ### Pick a template | Template | Columns | |----------|---------| | **Blank** | None. Add your own. | | **Start / Stop / Continue** | Start · Stop · Continue | | **Mad / Sad / Glad** | Mad · Sad · Glad | | **4 Ls** | Liked · Learned · Lacked · Longed For | | **What went well / What didn't / Action items** | What went well · What didn't go well · Action items | ### Create Click **Create**. The board opens, ready for cards. ::: The **Retro** home page lists your boards with their status (**active** or **closed**). If you pick a team in the team switcher, the list shows that team's retros. ## Columns People who can manage the board can shape it: - **Add a column**: click **Column**, give it a name, optionally pick an emoji and a color, and click **Add**. - **Rename** a column, or change its emoji and color, from its heading. - **Reorder** columns by dragging them by the handle. - **Delete** a column. All of its cards are deleted with it. ## Cards Type in a column's box ("What's on your mind?") to add a card. Cards do not show who wrote them, so people can be candid. On each card you can: - **Vote**: click **Agree** (thumbs up) or **Disagree** (thumbs down). The card shows the total score. Each person has one vote per card; clicking the same direction again removes your vote, and clicking the other direction switches it. - **React** with an emoji: 👍 ❤️ 🎉 🤔 😢 🚀 💯 🔥. Click a reaction again to remove yours. - **Comment**: open the card's comments and write one. - **Edit** or **Delete** it, with the icons on the card. Double-clicking a card's text also starts editing. You can edit and delete your own cards, and delete your own comments. People whose role can manage retros (`retro:board:write`, held by Owners, Admins and Members) can edit or delete anyone's cards and delete anyone's comments, which is how you moderate a board. ### Merge duplicate cards When two cards say the same thing, drag one onto the other in the same column. Confirm **Merge** and the dragged card's text is combined into the target card, and the dragged card is removed. ### Turn voting or comments off Use the **Voting on/off** and **Comments on/off** buttons in the board header. Both are on for a new board. Turning one off hides it on every card. ### Sort by votes **Sort by votes on** orders every column's cards highest score first, for everyone on the board. Cards with the same score keep their usual order. Select it again (**Sort by votes off**) to go back to the usual order. ## Action items The **Action Items** section below the columns holds the follow-ups the team agrees on. - Click **Add**, describe the action, optionally pick an **Owner** from your organization's members and a **Due date**, and click **Create**. - Change an item's owner or due date later with its edit button (**Edit owner and due date**). - Tick an action item to mark it done; tick again to reopen it. - The section shows how many are still open. An open item past its due date is marked **Overdue**. Action items stay with the board after it is closed. The **My retro actions** widget on your [home page](https://docs.evohub.io/your-home-page.md) lists the items you own, overdue first. ## Run it live Everyone on the board sees new cards, votes, reactions, comments and action items appear as they happen, without reloading. The header shows how many people are online. ## Use the timer Each board has one countdown that everyone on it sees, for example to timebox writing cards or discussing a column. :::steps ### Start it Click **Timer** in the board header. Pick **3 min**, **5 min**, **10 min** or **15 min**, or type a **Custom** length from 1 to 120 whole minutes, and click **Start timer**. ### Run it While it counts down, the header shows the time left. **Pause** and **Resume** it, add a minute with **+1 min**, or end it with **Stop**. ::: In the last minute the timer changes colour; at zero it shows **Time's up** until someone stops it. The countdown runs on EvoHub's clock, so everyone sees the same time even if a computer's clock is off, and it keeps running if you reload the page. People who can manage the board (`retro:board:write`) start and control the timer. Everyone else, guests included, sees it but cannot change it. A closed board's timer cannot be changed from the console. ## Invite guests with a share link People outside your EvoHub organization can join a retro through a share link. :::steps ### Copy the link Click **Share** in the board header. The link is copied to your clipboard. ### Send it Paste it into your chat or meeting invite. ::: Guests open the link without an EvoHub account. They can add cards, vote, react and comment. They cannot manage the board, add action items, edit or delete cards from the board, or export it. Anyone with the link can join, so share it only with the people you mean to. ### Share link modes A share link has three modes: | Mode | What guests can do | |------|-------------------| | `participate` (default) | View the board and take part, as described above. | | `read_only` | View the board only. | | `disabled` | Nothing. The link does not open the board. | The console always creates links in `participate` mode. To change the mode, or to revoke every existing link by issuing a new one, use the API with an [API key](https://docs.evohub.io/api-keys-and-scopes.md) that has the `retro:board:write` scope: ```bash # Make the link read-only curl -X PATCH https://evohub.io/api/v1/retro/boards//share \ -H "X-API-Key: $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"mode": "read_only"}' # Issue a new link; every old link stops working at once curl -X POST https://evohub.io/api/v1/retro/boards//share/rotate \ -H "X-API-Key: $EVOHUB_API_KEY" ``` The rotate call returns the new `share_url`. ## Who can see a retro Click **People** in the board header to open **Who can see this retro**. - **Who sees it without being named**: **Everyone in the organization**, the retro's team, or **Only the people named here**. - **People**: add someone from your organization with **Add a person…**, or remove them. Members of the retro's team are listed as **via team**. - **Teams**: give another team access with **Share with a team…**, or **Unshare** it. Naming someone only lets them see the retro. What they can do on it comes from their organization role. Owners and Admins can see every retro. ## End and reopen a retro When you are done, click **End Retro** and confirm. The board is closed: a banner says "This retrospective has been closed. No further changes can be made.", and nobody can add or change cards, votes, comments or action items. To continue, click **Reopen Retro**. ## Export to PDF Click **Export PDF** in the board header to download the board, with its columns, cards and action items, as a PDF. ## Delete a retro board On the **Retro** home page, hover over a board and click the trash icon, **Delete retro**, then confirm. Deleting a board removes its columns, cards, votes, comments and action items. Only Admins and Owners can delete retros (`retro:board:delete`). Members can create, run and close them, but not delete them. ## Permissions | Action | Permission | Built-in roles | |--------|-----------|----------------| | See retros | `retro:board:read`, `retro:card:read`, `retro:action:read` | Everyone | | Create a retro; manage columns, settings, people, sharing and the timer; end and reopen; moderate cards | `retro:board:write` | Owner, Admin, Member | | Add cards, vote, react, comment | `retro:card:write` | Owner, Admin, Member | | Add and update action items | `retro:action:write` | Owner, Admin, Member | | Delete a retro | `retro:board:delete` | Owner, Admin | Viewers can open the retros they can see, but the board is read-only for them. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). ## Related - [Boards](https://docs.evohub.io/boards.md) - [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) --- Source: https://docs.evohub.io/boards.md # Boards EvoHub Board is a Kanban board for your team's work: lists you drag cards between, with one extra rule an engineering team needs. If you want it, work cannot reach the done list until someone other than the people doing it has approved it. This page covers creating boards, working with cards, the review gate, who can see a board, and the archive. Boards live under **Board** in the EvoHub console. A board holds lists, and lists hold cards. There is no project layer: the board is the project. ## Create a board :::steps ### Start a new board In **Board**, click **New board** (or **Create a board**). ### Name it Enter a **Name**, for example `Q3 platform work`. ### Choose a card prefix Every card gets a reference made of the board's prefix and a number, such as `DEV-1`, `DEV-2`. Type two to six letters, or leave it blank and EvoHub picks one from the board's name. The prefix must be unused in your organization. ### Pick a template Choose how the board starts. See [Templates](#templates). ### Choose who sees it **Private** (only people you add, the default), **One team** (everyone in the team you pick, and nobody else), or **Everyone in the organization**. You can only pick a team you belong to; administrators can pick any team. ### Create Click **Create board**. ::: > [!WARNING] > A board's card prefix cannot be changed later. Card references are meant to be written into commit messages and stay valid, so choose it with care. ### Templates | Template | Lists | Review gate | |----------|-------|-------------| | **Blank** | None. Add your own. | No | | **Kanban** | To Do · In Progress · Done | No | | **Kanban with review** | To Do · In Progress · Review · Done | Yes | | **Sprint** | Backlog · To Do · In Progress · Review · Done | Yes | | **Bug triage** | Reported · Confirmed · Fixing · Verify · Closed | Yes | | **Product roadmap** | Ideas · Planned · Building · Shipped | No | | **Support queue** | Triage · Investigating · Waiting on customer · Resolved | No | | **Content** | Idea · Drafting · Review · Published | Yes | | **Hiring pipeline** | Applied · Screening · Interview · Offer · Hired | No | Templates already mark the right lists as the review list and the done list. You can change that afterwards. ## Lists - **Add a list**: click **Add a list** at the end of the board, type a name and click **Add list**. - **Reorder** lists by dragging them by their handle. - From a list's menu: **Rename**, set what the list is for (**An ordinary list**, **The review list** or **The done list**), choose a **Celebration…**, or **Archive list**. Archiving a list archives all the cards in it. See [The archive](#the-archive). ### The done list Mark one list as **The done list** to tell EvoHub that cards there are finished. A card that reaches it gets a **Completed** badge, and it is archived automatically after a while. See [Auto-archive](#auto-archive). ### Celebrations A list can play a short effect when someone drops a card into it: **Confetti**, **Hearts**, **Sparkles**, **Fireworks**, **Balloons**, **Bubbles**, **Petals**, **Applause** or **A ring of light**, or **Nothing**. It plays once, only on the screen of the person who moved the card, and not at all if their device is set to reduce motion. ## Cards Click **Add a card** at the top or bottom of a list, type a title ("What needs doing?"), and press Enter. Drag cards within a list or to another list. Cards never move between boards. Click a card to open it. A card can have: | Field | What it is for | |-------|---------------| | **Description** | Details of the work. | | **Assignees** | The people doing it. Click **Add** and pick someone from your organization. | | **Labels** | Board-wide tags, such as `urgent` or a sprint name. | | **Colour** | A colour stripe on the card's tile. | | **Due date** | When it is due. | | **Checklist** | Steps, ticked off one by one. The section shows progress, such as `Checklist · 2/5`. | | **Linked work** | Links to things in other EvoHub products: an On-Call alert, incident, schedule or escalation policy, an uptime monitor, a status page, a retro, a support ticket or another board. | | **Comments** | The conversation about the card. | | **History** | Everything that has happened to the card. Click **Show**. | To archive a card, open its options menu and choose **Archive card**. ### Comments You can edit and delete your own comments; an edited comment is marked "edited". People whose role can manage boards (`board:board:write`) can also delete anyone's comment, but nobody can edit someone else's. ### Labels Labels belong to the board, so the same label can go on many cards. Create them from a card's **Labels** section with **New**, or on the board's **Labels** page with **New label**. Label names are unique on a board, ignoring case. Deleting a label removes it from every card. ## Find cards Use **Search cards…** at the top of the board to search card titles. You can also type a card reference, such as `DEV-42`, or just its number. Click **Filters** to narrow the board by label, by due date (**Overdue**, **Due today**, **Due this week**, **No due date** or **Between two dates…**), or by when cards were created. **Clear all** removes the filters. ## The review gate The review gate makes sure work is checked by someone other than the people who did it before it counts as done. It is opt-in: a board only has a gate if one of its lists is marked **The review list**. How it works: 1. When a card enters the review list, it is **In review** (pending). 2. A reviewer opens the card and clicks **Approve**, or **Send back** with a note on what needs doing. 3. An approved card can move to the done list. If the board has a review list, a card cannot enter the done list without approval. 4. **Send back** returns the card to the list it came from, and the note is added to the card's comments. The rules: - **An assignee cannot approve their own card.** Anyone else with review permission can, including the person who created it. If you try, the console explains: "You cannot approve work assigned to you." - A card with no assignees can be approved by anyone with review permission. - Sending a card back is not restricted the same way: assignees can pull back their own work. - A card that is in review cannot be moved out of the review list except by approving or sending it back. - If an approved card is moved back to an ordinary list, its approval is cleared and it needs reviewing again. - While you drag a card, lists it cannot enter say why: "Needs review first" or "Waiting for review". Approving and sending back need `board:review:write`. Choosing which list is the review list or the done list needs both `board:review:write` and `board:board:write`. ## Who can see a board A board decides who **sees** it. What people can **do** on it comes from their organization role. | Visibility | Who sees it | |------------|------------| | **Private** | Only the people and teams on the board's **Members** page. | | **One team** | Everyone in that team, plus anyone named on the board. | | **Everyone in the organization** | Everyone in the organization. | Owners and Admins can see every board, including private ones. ### Share a board Open the board's **Members** page: - **Add member**: choose a person and click **Add to board**. They can now see the board. - **Share with a team**: pick a team. Everyone in it can see the board. Use **Unshare** to undo. - Remove someone from the board from their row. Members of the board's own team are listed as **Via team**. Adding people needs `board:member:write`. To change a board's name, description or visibility, open its **Settings** page and click **Save changes**. That needs `board:board:write`. ## The archive Cards leave a board through the archive, whether you archive them by hand, archive their list, or they are archived automatically. Open the board's **Archive** page to search archived cards and **Restore to the board** any of them to the board. ### Auto-archive Cards that sit in the board's done list are moved to the archive on their own after a number of days. Set it under **Archive after** on the board's **Settings** page: between 1 and 365 days (365 is the default). It cannot be turned off. Only the done list is affected; cards in other lists are never archived automatically, however old they are. ### Retention Archived cards stay readable and restorable for two years. Two years after being archived, a card is deleted for good, with its comments, checklist and history. ## Archive a board Boards are archived, not deleted. On the board's **Settings** page, click **Archive board** and confirm. The board disappears from everyone's list of boards. Its lists, cards and comments are kept, but there is no way back to it from the console. Only Admins and Owners can archive boards (`board:board:delete`). Members see the button disabled with the note "Only admins can archive boards". ## Live updates Everyone looking at a board sees cards and lists change as others work, without reloading. ## Permissions | Action | Permission | Built-in roles | |--------|-----------|----------------| | See boards, lists, cards and the archive | `board:board:read`, `board:list:read`, `board:card:read` | Everyone | | Create a board; rename it, change its visibility and auto-archive; manage labels; delete others' comments | `board:board:write` | Owner, Admin, Member | | Add, rename, reorder and archive lists; set a celebration | `board:list:write` | Owner, Admin, Member | | Add and change cards, and everything on them; restore from the archive | `board:card:write` | Owner, Admin, Member | | Add people and teams to a board | `board:member:write` | Owner, Admin, Member | | Approve or send back cards in review | `board:review:write` | Owner, Admin, Member | | Archive a board | `board:board:delete` | Owner, Admin | Viewers can see boards that are visible to them but cannot change anything. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). ## Related - [Retro boards](https://docs.evohub.io/retro-boards.md) - [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) --- Source: https://docs.evohub.io/docs-overview.md # Docs overview EvoHub Docs publishes your product documentation as a site of its own, on your own domain. You write pages in Markdown in the EvoHub console (or in a GitHub repository), arrange them into sections, and publish them when they are ready. This documentation is itself an EvoHub Docs site. The Docs console lives at [https://evohub.io/docs-admin](https://evohub.io/docs-admin). ## How a site is organized | Concept | What it is | | --- | --- | | **Docs site** | One documentation site, with its own name, logo, theme, domain and settings. | | **Version** | A content space of the site, such as `v1` and `v2`. Every site starts with one version, and most sites never need more. | | **Section** | A heading in the navigation that groups pages. Sections have no content of their own. | | **Page** | One Markdown document. A page sits in a section or at the top level. | | **API reference** | An OpenAPI document rendered as reference pages. See [API references and AI](https://docs.evohub.io/api-references-and-ai.md). | | **Snippet** | Reusable Markdown included in pages with `{{snippet:name}}`. See [Writing pages](https://docs.evohub.io/writing-pages.md). | A site can belong to the whole organization or to one team. A team site is visible only to that team and to organization Admins and Owners. ## Who can do what Docs has no per-site members or roles. What you can do on every docs site of your organization comes from your organization role: | Permission | Lets you | | --- | --- | | `docs:site:read` | Open sites, pages, drafts, history and analytics. | | `docs:page:write` | Write and edit pages, sections, snippets, API references and files; open change requests. | | `docs:page:publish` | Publish and unpublish, approve and merge change requests, roll back. | | `docs:site:write` | Create sites and change their settings: domain, theme, navigation, review, access, GitHub. | | `docs:site:delete` | Delete a whole site. | Members get everything except deleting a site; Viewers can read. Deleting a site is for Admins. To split the work, build a custom role from the **Docs writer**, **Docs reviewer** or **Manages docs sites** presets. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). ## Create a site :::steps ### Open Docs In the EvoHub console, open **Docs**. The **Docs sites** page lists every site you can reach. ### Create the site Choose **New site**, give it a **Name** and an **Address**, and select **Create site**. The address is a short identifier (lowercase letters, digits and hyphens) that is unique across EvoHub. It names the site in the console and the API; it is not a web address. You can also start with **From template** or bring existing docs in with **Import**. See [Import and export](https://docs.evohub.io/import-and-export.md). ### Add pages In the site editor, add sections and pages from the navigation tree on the left. New pages start as drafts. ### Publish Publish each page when it is ready, then open **Settings** and choose **Publish site** on the banner above the tabs. ### Connect a domain A site answers only on its own domain. Until you connect one, use **Preview** in the console. See [Connect a custom domain](https://docs.evohub.io/docs-custom-domain.md). ::: ## Drafts and publishing Every page has two copies: - The **draft** is what you edit. **Save draft** stores your changes without showing them to anyone. - The **live copy** is what readers see. **Publish** (or **Publish changes** for a page that is already live) copies the draft to the live copy. Editing a published page changes nothing on the site until you publish again. The editor marks a page with **Unpublished changes** while the two copies differ. **Unpublish** takes a page off the site and keeps its draft. Two things must be true before a reader sees a page: the page is published, and the site itself is **Published** (the banner at the top of **Settings**; **Take offline** reverses it). A site that is not published is visible only to your organization. If the site requires review, pages go live only through an approved change request. See [Review and change requests](https://docs.evohub.io/review-and-change-requests.md). ### Editing together The editor shows when someone else is editing the same page. If two people save the same page, the second save is stopped and you choose to keep your version, take theirs, or copy what you need across. Nothing is overwritten silently. ## Revisions and rollback Every publish is kept as a revision; the newest 50 per page are kept. Open the page's publish history to see them. For each revision you can: - **Restore** — copy that version into the draft. It does not publish. - **Roll back** — make that version live again right away. On a site that requires review, a rollback opens a change request instead. A rollback refuses to overwrite draft work that was never published unless you confirm that you want to discard it. ## Navigation The editor's navigation tree sets the order readers see: move pages and sections up and down, rename sections, and move pages between sections. Deleting a section never deletes pages; its pages move to the top level. Under **Settings**, **Navigation** you shape the rest of the site: - **Tabs** across the top, each showing some sections or linking somewhere else. - **Header** links and an optional highlighted button. - **Footer** columns of links and social links. - **Section icons** shown before each section title. - **Page footer**: show **Last updated** and an **Edit this page** link built from a template such as `https://github.com/acme/docs/edit/main/{slug}.md`. - **Home page**: the **First page**, **A chosen page**, or a **Landing page** with a headline, subtitle, button and cards. Navigation and theme changes apply to the live site as soon as you save them. Each page also has its own settings (the settings button in the editor): SEO title, description, link preview image, canonical address, **Hide from search engines**, **Sidebar title**, icon and **Hide from navigation**. Page settings go live when the page is published. ## Theme **Settings**, **Brand** holds the logo (PNG, JPEG or WebP, up to 2 MB), **Show the site name next to the logo**, and the theme: fonts (loaded from Google Fonts only when you pick one), the accent colour, corner radius, background, code theme, colour scheme (follow the reader's device, or always light or dark) and favicon. ## Versions A version is a separate set of sections, pages, snippets, API references and redirects. The logo, theme, layout, domain, files and languages are shared by every version. Manage them in **Settings**, **Versions**: - **New version** creates an empty version or a copy of an existing one. - **Make default** chooses which version the short addresses (`/page`) show. Other versions live under `//page`. - Each version can be published or not, tagged (for example **Latest** or **LTS**), marked **Deprecated** with a banner that points readers to the default version, or hidden from search engines. A site holds up to 20 versions. Readers switch versions from the site's version switcher. ## Languages Pages are written in the site's default language first. In **Settings**, **Languages** add up to 10 other languages; each page can then be translated in the editor's language tabs. A translation keeps its page's address under a language prefix (`/de/page`) and its place in the navigation, and is published like any other page. The **Translations** page shows, for every page, which languages are missing, outdated (the source changed since) or up to date. For untranslated pages, choose to **Show the default language with a note** or **Hide untranslated pages**. Under **Interface text** you can reword the site's own labels, such as "On this page", per language. EvoHub does not currently translate pages automatically; translations are written by people. ## Other site tools - **Assets** — images, PDFs and icons uploaded to the site. See [Writing pages](https://docs.evohub.io/writing-pages.md#files-and-images). - **Redirects** — keep old links working. A redirect applies only where no page answers. Moving a published page adds one automatically. - **Link health** — finds broken links on the published site. Links are checked after every publish; **Check now** runs it on demand, optionally including external links. - **Feedback** — readers' answers to "Was this page helpful?", for every version and language. See [API references and AI](https://docs.evohub.io/api-references-and-ai.md#page-feedback). - **Activity** — who changed what on the site. ## Delete a site **Settings**, **General**, **Delete site** removes the site with every page and revision. Only Admins (or a custom role with `docs:site:delete`) can do this. Download an export first if you want a copy. See [Import and export](https://docs.evohub.io/import-and-export.md). ## Billing Docs is billed by usage, not per seat: any number of people can write and review. See [How billing works](https://docs.evohub.io/how-billing-works.md) for what is metered. ## Related - [Writing pages](https://docs.evohub.io/writing-pages.md) - [Review and change requests](https://docs.evohub.io/review-and-change-requests.md) - [Connect a custom domain](https://docs.evohub.io/docs-custom-domain.md) - [Docs as code with GitHub](https://docs.evohub.io/github-sync.md) --- Source: https://docs.evohub.io/writing-pages.md # Writing pages Pages in EvoHub Docs are written in Markdown, with a small set of components for callouts, tabs, steps and the like. This page lists everything the renderer understands, how to reuse text with snippets, and how to add images and files. The same syntax works in the console editor and in files synced from GitHub. ## The editor Open a page from the site's navigation tree. The editor has the Markdown on one side and a live preview on the other. The toolbar adds formatting, an **Insert** menu for every component below, an upload button, and the **Markdown cheat sheet** (with a second tab, **Writing in Git**, for files in a repository). Raw HTML is never rendered. Use the components below instead. The page title is shown above the page, so do not repeat it as a `# Heading` in the body. Start sections at `##`. ## Basic Markdown | Element | Markdown | | --- | --- | | Headings | `## Install` and `### On macOS` | | Bold, italic, strikethrough | `**bold**`, `_italic_`, `~~struck~~` | | Link to a page | `[Quickstart](/quickstart)` | | Link to a section | `[Install the CLI](/quickstart#install-the-cli)` | | Lists | `- Item` and `1. First` | | Task list | `- [x] Done` and `- [ ] To do` | | Table | Columns separated by `\|`, with a `---` row under the header | | Footnote | `Billed hourly.[^1]` and, anywhere below, `[^1]: Rounded up.` | Every heading gets an anchor: lowercase, words joined by hyphens. Code blocks are highlighted by language and get a **Copy** button. ## Callouts A blockquote whose first line is a marker becomes a callout. Text after the marker is its title. ```markdown > [!NOTE] > Changes take a minute to show up. > [!TIP] Faster builds > Turn on caching in the settings. > [!WARNING] > Rotating the key signs everyone out. ``` The five kinds are `[!NOTE]`, `[!TIP]`, `[!INFO]`, `[!WARNING]` and `[!DANGER]`. GitHub's `[!IMPORTANT]` reads as info and `[!CAUTION]` as danger. ## Tabs ```markdown :::tabs ::tab{title="npm"} npm install acme ::tab{title="pnpm"} pnpm add acme ::: ``` ## Code group Code blocks in tabs that share one **Copy** button. The label in brackets after the language names the tab. ````markdown :::code-group ```bash [npm] npm install acme ``` ```bash [pnpm] pnpm add acme ``` ::: ```` ## Steps Numbered steps; every `###` heading inside starts one. ```markdown :::steps ### Install the CLI Run the installer. ### Sign in Use your account. ::: ``` ## Cards A grid of cards, one to four columns. A card with `href` is a link; `icon` takes an icon name such as `rocket`, `book`, `code`, `settings`, `key`, `shield` or `zap`. ```markdown :::cards{cols=2} ::card{title="Quickstart" icon="rocket" href="/quickstart"} Up and running in five minutes. ::card{title="API" icon="code" href="/api"} Every endpoint, with examples. ::: ``` ## Accordion A section that folds away. Add `open` to show it unfolded. ```markdown :::details{title="How is usage billed?"} By the hour. ::: ``` ## Columns ```markdown :::columns{cols=2} ::col Left column. ::col Right column. ::: ``` ## Badge A small inline label. Colours: `gray`, `blue`, `green`, `yellow`, `red`, `purple` and `accent`. ```markdown Webhooks :badge[New]{color=blue} ``` ## Video embed YouTube, Vimeo and Loom videos are embedded. Any other address shows as a link. ```markdown ::embed{url="https://www.youtube.com/watch?v=VIDEO_ID" title="Product tour"} ``` ## Diagrams and math Mermaid diagrams are drawn from a `mermaid` code block: ````markdown ```mermaid flowchart LR A[Alert] --> B[On-call] --> C[Resolved] ``` ```` Math uses KaTeX syntax: `$\pi r^2$` inside a sentence (no space after the opening `$` or before the closing one), or a block between `$$` lines. ## Snippets A snippet is a piece of Markdown — a support notice, a version table — written once and included in any page. Change the snippet and every page that uses it changes. :::steps ### Create the snippet Open **Snippets** in the site's menu, name it (lowercase letters, digits and single hyphens, for example `support-contact`), and write its Markdown. ### Publish it **Save draft**, then **Publish**. Like pages, snippets have a draft and a live copy, and readers see the live copy. ### Include it Put `{{snippet:support-contact}}` on a line of its own in any page. The Insert menu lists your snippets. ::: Snippets are not expanded inside code, and a snippet cannot include another snippet. An unknown name is shown as written. Unpublishing a snippet makes pages show the reference as plain text. A site holds up to 200 snippets per version. ## Reader variables On a private site that signs readers in with JWT, `{{user.name}}`, `{{user.email}}` and any other claim of the reader's token are replaced with that reader's details when the page is shown. On an EvoHub members site, only `{{user.name}}` is filled in: ```markdown Welcome back, {{user.name}}. Your plan: {{user.plan}}. ``` On public sites and in previews they are left as written. See [Private docs](https://docs.evohub.io/private-docs.md). ## Files and images Paste or drop an image into the editor, or use the upload button, and EvoHub uploads it and inserts the Markdown. Uploaded PDFs are inserted as a link. | Type | Largest file | | --- | --- | | PNG, JPEG, WebP, GIF | 5 MB | | SVG, ICO | 1 MB | | PDF | 20 MB | SVG files are checked for anything that could run script and refused if they contain it. A site holds up to 500 MB and 2,000 files. The **Assets** page lists every file with where it is used. **Copy URL** and **Copy Markdown** give you the reference. Deleting a file that a page still uses asks you to confirm, because it breaks those pages. > [!NOTE] > Uploaded files are reachable by their address even on a private site. ## Page settings The settings button in the editor holds what is not part of the text: SEO title, description (up to 300 characters), link preview image, canonical address, **Hide from search engines**, **Sidebar title**, icon, **Hide from navigation**, and, on private sites, **Audience**. Settings are drafted with the page and go live when it is published. ## Writing in a repository If your site is connected to GitHub, the same Markdown lives in files, with these settings as front matter. See [Docs as code with GitHub](https://docs.evohub.io/github-sync.md). ## Related - [Docs overview](https://docs.evohub.io/docs-overview.md) - [Docs as code with GitHub](https://docs.evohub.io/github-sync.md) - [API references and AI](https://docs.evohub.io/api-references-and-ai.md) --- Source: https://docs.evohub.io/review-and-change-requests.md # Review and change requests A change request bundles drafts — pages, snippets, API references and navigation changes — so they can be reviewed and published together. You can use change requests on any site. When a site requires review, they are the only way anything goes live. ## Turn on required review In the site's **Settings**, **Review**: 1. Turn on **Require review before publishing**. 2. Pick **Approvals needed to merge** (1 to 3). While review is on, nobody publishes directly — Admins and Owners included. **Publish**, **Unpublish**, deleting a live page and changing a live page's address are refused; those changes go through a change request. Changes to the navigation (order and sections) also wait for a change request: readers keep seeing the navigation as it was until one is merged. Turning review off is a site setting, so it needs permission to manage the site. When you turn it off, the navigation goes live as it is arranged in the editor. ## Who can review Authorization comes from organization roles, not from the site: - Anyone with `docs:page:write` can edit drafts and open change requests. - Anyone with `docs:page:publish` can approve, request changes, merge and undo a merge — whether or not they were asked. - Nobody approves their own change request. - Reviews, merges and undoing a merge must be done by a person signed in to EvoHub. An API key cannot approve or merge, whatever its scopes. The **Docs reviewer** preset gives a role exactly the approve-and-publish side. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). ## Open a change request :::steps ### Make your edits Edit pages, snippets or API references and **Save draft**. Reorder the navigation if you need to. ### Start the request Open **Change requests** in the site's menu and choose **New change request**. On a site that requires review, the page editor's **Open change request** button (in place of **Publish**) starts one for the page you are on. ### Pick the changes Every draft that differs from what is live is listed — pages, translations, snippets, API references and **Navigation**. Tick the ones to include. A change can also take a live page off the site (**Unpublish**). ### Describe it and ask for review Give it a **Title** and an optional **Description**, add **Reviewers**, and choose **Open change request**. Reviewers get a notification in Docs; they are whom you ask, not a gate. ::: On a site with several versions, a change request belongs to one version and publishes to it. ## Review a change request The change request shows each item as a diff of the live text against the draft (the navigation as an outline), plus a **Settings** block when page settings change. You can comment on the request as a whole or on a single item, and reply in threads. At the bottom, leave an optional comment and choose **Approve** or **Request changes**. Any current request for changes blocks the merge. ### Approvals go stale An approval is tied to exactly what the reviewer saw. If anything in the request changes after that — a draft is edited, an item is added or removed, the request is refreshed — earlier approvals are marked **Reset by later edits** and stop counting. Ask for another review. ## Merge When the request has enough current approvals and nothing is waiting, it shows **Approved and ready to merge** and the **Merge** button. Merging publishes every item at once, in one step: either everything goes live or nothing does. Each published page gets a revision that records the change request. ### Outdated requests Each item remembers the live copy it was drawn against. If someone publishes one of those pages some other way after the request was opened, the request is marked **Outdated** and cannot be merged. **Refresh** re-bases it on the current live copies; approvals reset. ### Undo a merge A merged request offers **Undo merge**, which puts back exactly what was live before it — pages, snippets, API references and navigation. Undo is refused if any of those items was published again since. ### Close and reopen **Close** sets a request aside without publishing; **Reopen** brings it back. The author or anyone who can publish can do either. ## Preview links A preview link shows the whole site with the request's drafts in place, for people who do not use the console. 1. In the change request, under **Preview link**, choose **Create preview link**. 2. Copy the link right away; it is shown only once. Anyone with the link sees the drafts, including pages limited to an audience on a private site, so share it like a password. Preview pages are never indexed or cached, and every link inside stays within the preview. The link stops working when the request is merged or closed, after 30 days, or when you choose **Turn off** or **New link**. Preview links open on the site's own domain, so the site needs an active custom domain. See [Connect a custom domain](https://docs.evohub.io/docs-custom-domain.md). ## Rollbacks under review On a site that requires review, rolling a page back to an earlier revision puts that revision in the draft and opens a change request for it instead of publishing. ## GitHub and imports under review When a site requires review, a GitHub sync opens (or updates) one change request instead of publishing, and an import into an existing site arrives as one change request. See [Docs as code with GitHub](https://docs.evohub.io/github-sync.md) and [Import and export](https://docs.evohub.io/import-and-export.md). ## Review requests Change requests waiting for review are listed on the site's **Change requests** page, and in the **Awaiting my approval** widget you can add to your [home page](https://docs.evohub.io/your-home-page.md). Docs does not email them. ## Related - [Docs overview](https://docs.evohub.io/docs-overview.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) - [Docs as code with GitHub](https://docs.evohub.io/github-sync.md) --- Source: https://docs.evohub.io/docs-custom-domain.md # Connect a custom domain A docs site has exactly one public address: a subdomain you own, such as `docs.example.com`. EvoHub does not give sites an address of its own, so until a domain is connected and active, the site is visible only through **Preview** in the console. This page shows how to connect one. You need permission to manage the site (`docs:site:write`) and access to your domain's DNS. ## Before you start - Use a **subdomain** with at least three labels, like `docs.example.com`. Apex domains (`example.com`) are not supported. - One hostname serves one site across EvoHub. A hostname already used by another site or a status page is refused. - The site's pages show only once the site and the pages are published. You can connect the domain first and publish later. ## Connect the domain :::steps ### Enter the hostname Open the site's **Settings**, **Domain**. Under **Your domain**, type the hostname (for example `docs.example.com`) and choose **Connect domain**. ### Add the DNS records EvoHub shows two records with **Type**, **Name** and **Value**, each with a **Copy** button: | Type | Name | Value | | --- | --- | --- | | CNAME | `_acme-challenge.docs.example.com` | The value shown in the console | | CNAME | `docs.example.com` | `cname.evohub-dns.com` | Add the `_acme-challenge` record first and wait for the status to turn green, then add the routing CNAME. That way the domain never goes live without a certificate. ### Wait for validation Choose **Re-check** to refresh the status. When it shows **Verified · SSL active**, the site is live at `https://docs.example.com`. ::: > [!TIP] > If your DNS is proxied through Cloudflare, set SSL to **Full** (not Flexible). ## Preview before connecting a domain You can see the docs site exactly as readers will, before it has a domain or without one. Open the site in the console and, at the top of the editor, choose **Preview site**. The docs site opens in a new tab, drawn by the same renderer as the live site, with a thin banner on top: *Preview — only your team can see this. Connect a domain to publish.* - Only signed-in members of your organization who can read the docs site can open a preview. The link from the button works once and only for two minutes; after that, choose **Preview site** again. - A preview lasts 15 minutes from the moment you ask for it. Links inside it stay within the preview. - Previews are never indexed or cached, are not counted in analytics, and are not billed. - Nothing can be written from a preview: the feedback widget is hidden. It shows the published pages of the default version, published site or not; to see drafts, use **Preview** on a page or a change request's preview link. ## Domain status | Status | Meaning | | --- | --- | | **Not connected** | No domain yet. | | **Pending DNS** | The records are not found yet. | | **Validating** | The records are found and the certificate is being issued. | | **Verified · SSL active** | The site is live on the domain with a certificate. | | **Action needed** | The domain stopped being served — usually a record was removed or the certificate expired. | For **Action needed**, check that both records are still in your DNS exactly as shown, then **Re-check**. If it stays red, remove the domain and connect it again. Keep both records in place for as long as the site uses the domain. Removing them takes the site off the address. ## What the domain is used for Everything a reader reaches is on this domain: the pages, search, `llms.txt`, the `.md` copies of pages, the MCP server, sign-in for private sites and change request preview links. Links EvoHub builds for you — a preview link, a test sign-in link — need the domain to be active. ## Change or remove the domain - To move to a different hostname, choose **Remove domain**, then connect the new one. The old hostname stops serving the site. - **Remove domain** disconnects the hostname. The site stays in the console and keeps its pages. ## Related - [Docs overview](https://docs.evohub.io/docs-overview.md) - [Private docs](https://docs.evohub.io/private-docs.md) - [Custom domain for a status page](https://docs.evohub.io/status-page-custom-domain.md) --- Source: https://docs.evohub.io/github-sync.md # Docs as code with GitHub You can keep a docs site's pages in a GitHub repository. Every push to the connected branch updates the site, and, if you choose, edits made in the EvoHub console go back to the repository as pull requests. This page describes the repository layout EvoHub reads and how to connect it. This documentation is maintained this way. ## The repository layout EvoHub reads one folder of one branch — the **docs root**, for example `docs/`. Inside it: ```text docs/ ├── index.md first page ("Introduction") ├── getting-started.md a top-level page ├── guides/ a folder of pages is a section │ ├── _category_.json {"label": "Guides", "position": 3} │ ├── 01-install.md │ ├── 02-configure.md │ └── 03-deploy.md ├── reference/ │ ├── _category_.json │ └── components.md ├── images/ │ └── architecture.png linked from pages by relative path ├── _snippets/ │ └── support.md the snippet {{snippet:support}} └── _openapi/ └── orders.yaml an API reference at /orders ``` ### Pages and sections - Every `.md`, `.mdx` or `.markdown` file is a page, except files in folders whose name starts with `_`. - Files directly in the docs root are top-level pages. - A folder that holds pages is a section. A folder inside a section becomes a section of its own, listed right after its parent. A folder with no pages (like `images/`) is not a section. ### Titles and addresses - **Title**: the front matter `title`, else the first `# Heading` (which is then left out of the body), else the file name. - **Address (slug)**: the front matter `slug`, else the file name without its numeric prefix and extension. `01-install.md` is `/install`; `index.md` takes its folder's name, and the root `index.md` is `/introduction`. Slugs are unique within a site, so give pages in different folders different file names (or set `slug`). ### Order Pages within a folder are ordered by `sidebar_position` in the front matter, then a numeric prefix of the file name (`01-`, `1_`, `01.`), then `index.md` or `README.md` first, then the file name. Sections are ordered by `position` in their `_category_.json`, then a numeric prefix of the folder name, then the folder name. `_category_.json` also names the section: ```json {"label": "Guides", "position": 3} ``` Without it, the section is named after the folder (`getting-started` becomes "Getting started"). ### Front matter A YAML block between two `---` lines at the top of a file. Every key is optional: ```yaml --- title: Deploy to production slug: deploy sidebar_position: 3 sidebar_label: Deploy description: Ship a release to production from your machine or from CI. seo_title: How to deploy Acme to production og_image_url: https://acme.example/og/deploy.png canonical_url: https://acme.example/docs/deploy noindex: false hidden: false icon: rocket --- ``` | Key | What it does | | --- | --- | | `title` | The page title. | | `slug` | The page's address. | | `sidebar_position` | Its place among its siblings, lowest first. | | `sidebar_label` | Shorter text for the navigation. | | `description` | Text for search results and link previews (up to 300 characters). | | `seo_title` | The browser tab and search result title, when it should differ. | | `og_image_url` | An `https` image for link previews. | | `canonical_url` | Where the original of the page lives, if elsewhere. | | `noindex` | `true` keeps the page out of search engines. | | `hidden` | `true` leaves the page out of the navigation; it stays reachable by its address. | | `icon` | An icon shown next to the page in the navigation. | | `audience` | A list (or comma-separated text) of groups allowed to see the page on a private site. See [Private docs](https://docs.evohub.io/private-docs.md). | Other keys are ignored. Quote strings that contain a colon: `title: "Deploy: the basics"`. ### Links, images, snippets and API references - **Links** between pages use the relative path to the file, as on GitHub: `[Configure](./02-configure.md)`. They become links to the page's address, whatever its slug. - **Images** and PDFs are linked relative to the file: `![Architecture](../images/architecture.png)`. Files a page links to are uploaded with the sync; files nobody links to are not. - **Snippets**: `_snippets/.md` becomes the snippet ``, included with `{{snippet:}}` on a line of its own. - **API references**: every `.yaml`, `.yml` or `.json` file in `_openapi/` is an OpenAPI 3.0 or 3.1 document. It becomes an API reference named after its `info.title`, at the address of its file name. - Any other folder starting with `_` is skipped; use one for drafts or partials. Page bodies use the same Markdown and components as the console editor. See [Writing pages](https://docs.evohub.io/writing-pages.md). ### Start from the starter The **Docs as code starter** is a small repository that shows every rule above. Download it from the site's **Settings**, **GitHub** tab (**Download the starter (.zip)**), or create a site from the **Docs as code starter** template to see it rendered. ## Formats other than plain Markdown A connection reads plain Markdown by default. It can also read a repository written for Mintlify (`mint.json` or `docs.json`), GitBook (`SUMMARY.md`), ReadMe (`_order.yaml` per category) or Docusaurus (`sidebars.js`, `_category_.json`), or detect the format automatically. Change it under **Format** in the GitHub tab; saving a different format syncs the repository again and keeps pages in place by matching file paths. When EvoHub pushes back to a repository in one of these formats, it writes that format's components and navigation where an equivalent exists. A component the format has no equivalent for is written in EvoHub syntax and listed as a warning on the push. Docusaurus `sidebars.js` is never written; keep it in step yourself. ## Connect a repository You need permission to manage the site (`docs:site:write`), and you must be signed in as a person. You also need to be able to install a GitHub app on the repository. :::steps ### Start from the site Open the site's **Settings**, **GitHub** and choose **Connect GitHub**. ### Install the EvoHub app on GitHub GitHub opens. Install the EvoHub app on your account or organization and give it access to the repository. If your GitHub organization requires an owner's approval, connect again once it is approved. ### Choose what to sync Back in EvoHub, choose the **Repository**, the **Branch** and the **Folder** (for example `docs`; leave it empty for the repository root). ### Decide about pushing back Turn on **Push edits to GitHub as pull requests** if edits made in EvoHub should go back to the repository. You can change this later. ### Save Choose **Save and sync**. The first sync starts right away. ::: The GitHub tab then shows the repository, branch, folder and format, the result of the **Last sync** and **Last push**, and any warnings for individual files. **Sync now** runs a sync by hand. ## What a sync does A sync runs on every push to the connected branch that touches the folder. - New files become pages, changed files update their pages, and the navigation follows the folders and order rules. - A deleted file's page is unpublished and deleted. - Snippets and API references are created and updated, but never deleted by a sync. Remove them in EvoHub. - An empty folder makes no section. - On a site that requires review, a sync does not publish. It opens one change request — or updates the one already open — with the pages waiting, unpublish items for removed files, and the navigation if it changed. See [Review and change requests](https://docs.evohub.io/review-and-change-requests.md). ## Pushing edits back With **Push edits to GitHub as pull requests** on, publishing in EvoHub (including merging a change request, unpublishing, deleting, rolling back) writes the live pages back to the repository: - Commits go to a branch named `evohub/` and a pull request titled "Docs edits from EvoHub" is opened if none is open. Later edits are added to the same branch while the pull request is open. - Your connected branch and the repository's default branch are never written to directly. You review and merge the pull request. - New pages get a file in their section's folder; existing files are updated in place. ## Conflicts If the same page changed both in EvoHub and in its file on GitHub, nothing is overwritten. The page is flagged in the navigation tree and in the editor, and the GitHub tab lists it. For each conflicting page choose: - **Keep EvoHub's** — the next push replaces the file with the EvoHub version. - **Take GitHub's** — the repository's file replaces the page's draft (and the live page, unless the site requires review). Your draft edits are lost. ## Versions and languages - A site with several versions has one connection per version. Use a branch per version (`main` for the latest, `v1` for the old one, the same folder on each) or one branch with a folder per version (`docs/v2/`, `docs/v1/`). Pushes for a version other than the default go to `evohub/-`. - Translations are not synced. The repository holds the site's default language; write translations in EvoHub. ## Disconnect **Disconnect** in the GitHub tab removes the link between pages and files. The pages stay as they are. If the app is removed or loses access on GitHub, the connection shows as disconnected and **Reconnect** picks up where it left off. ## Related - [Writing pages](https://docs.evohub.io/writing-pages.md) - [Import and export](https://docs.evohub.io/import-and-export.md) - [Review and change requests](https://docs.evohub.io/review-and-change-requests.md) --- Source: https://docs.evohub.io/import-and-export.md # Import and export You do not have to start a docs site from an empty page. Import a `.zip` of your existing docs, start from a template, or connect a GitHub repository. You can also export any site as plain Markdown files to back it up or move it. ## Import a .zip ### Supported sources | Choose | What to zip | | --- | --- | | **Mintlify** | The folder that holds `mint.json` or `docs.json`, with its `.mdx` pages and images. | | **GitBook** | The repository or space export that holds `SUMMARY.md`, including the `.gitbook` folder for images. | | **ReadMe** | The folder from ReadMe's Markdown export or the `rdme` CLI, with one folder per category. | | **Docusaurus** | The project, or just its docs folder, with `sidebars.js` and any `_category_.json` files. | | **Markdown folder** | Any folder of `.md` or `.mdx` files. Folders become sections; images they link to come along. | | **EvoHub export** | A `.zip` downloaded from another EvoHub docs site. It has an `evohub.json` at the top. | | **Detect automatically** | EvoHub looks for `mint.json`, `SUMMARY.md`, `sidebars.js`, `evohub.json` and the like, and falls back to a plain Markdown folder. | Components from those tools (callouts, tabs, cards and so on) are converted to their EvoHub equivalents where one exists. A Markdown folder follows the same layout rules as GitHub sync; see [Docs as code with GitHub](https://docs.evohub.io/github-sync.md#the-repository-layout). ### Run the import You need `docs:page:write`. Creating a new site also needs `docs:site:write`. :::steps ### Choose the archive In Docs, choose **Import** on the **Docs sites** page (or **Import** in an open site's menu). Drop a `.zip` of up to 50 MB, pick **Where is it from?**, and choose **Analyze archive**. ### Check what was found EvoHub reads the archive without creating anything and shows the pages and sections it found, the number of images and files and snippets, and any warnings per file. ### Choose where it goes - **New site** — give it a **Name** and **Address**. The pages are published right away, but the site stays private until you publish it. - **Existing site** (or **Into this site**) — pick the **Site** and, if it has several, the **Version**. Everything arrives as drafts, in new pages and sections. Existing pages are never touched and nothing goes live. A page whose address is already taken gets a numbered address (`-2`), and links to it are rewritten. If the site requires review, one change request is opened with the imported pages. ### Import Choose **Create site and import** or **Import into site**. A large archive can take a minute; the page updates by itself. When it is done, **Open site** (or **Open change request**). ::: Images and files the pages link to are uploaded to the site, counted against its file storage, and identical files are stored once. Only the person who started an import can see it. ## Start from a template On the **Docs sites** page choose **From template**, pick one, and give the new site a name and address. The template's pages (and API reference, if it has one) are published, and the site stays private until you publish it. | Template | What you get | | --- | --- | | **Docs as code starter** | The layout EvoHub reads from GitHub: folders, front matter, every component, a snippet and an OpenAPI file. | | **API documentation** | A getting-started path, guides on pagination, errors, rate limits and webhooks, and a full API reference from an OpenAPI document. | | **Product guide** | A user guide for a product: first steps, core concepts, everyday tasks, integrations, roles and billing. | | **Help center** | Short answers to common customer questions, behind a landing page with a search-first hero and topic cards. | | **Developer platform** | Guides and concepts for developers, an API reference tab and a changelog linked from the header. | Replace the example content with your own. ## Export a site In the site's **Settings**, **General**, choose **Download as Markdown (.zip)**. The archive contains, for the version you are working on: - every page, as the draft you are editing, with its settings as front matter; - snippets and API references; - the site's layout (tabs, header, footer, home page); - uploaded images and files; - translations, if the site has other languages; - an `evohub.json` describing the structure. Import the `.zip` into another EvoHub site with **EvoHub export** to reproduce the tree, titles, addresses, order, text and settings, or keep it as a backup. Anyone who can read the site can export it. ## Related - [Docs overview](https://docs.evohub.io/docs-overview.md) - [Docs as code with GitHub](https://docs.evohub.io/github-sync.md) - [Review and change requests](https://docs.evohub.io/review-and-change-requests.md) --- Source: https://docs.evohub.io/private-docs.md # Private docs A docs site is public by default. You can make it private so that readers must sign in first, and limit single pages to some readers. This page covers the four access modes, page audiences, reader variables and read tokens for AI tools. Access is set in the site's **Settings**, **Access**, which needs permission to manage the site (`docs:site:write`). Authors in the console are not affected: anyone whose organization role allows reading docs sees every draft and page there. ## Choose who can read Under **Who can read the published site**, pick one: | Mode | Who gets in | | --- | --- | | **Public** | Anyone with the link. Search engines index the site. | | **Password** | Readers who enter one shared password. Good for a partner or an early-access group. | | **EvoHub members** | People in your EvoHub organization, optionally only some teams or roles. | | **Your own sign-in (JWT)** | Readers your own app signs in and sends to the site with a signed token. Pages can be personalised. | Then choose **Save changes**. On any private site: - The site is not indexed by search engines and has no sitemap. - **Session lifetime** sets how long a reader stays signed in: 1 hour, 8 hours, 24 hours, 3 days, 7 days or 30 days. - **Sign everyone out** ends every reader's session at once. Changing who can read, the teams or roles, or the JWT key also signs everyone out. - Analytics and the "Was this page helpful?" widget keep working for signed-in readers. - Uploaded files and images stay reachable by their address. - Change request preview links keep working with their own token and show every page, so share them like a password. Readers sign in on the site's own domain, so a private site needs an active custom domain. See [Connect a custom domain](https://docs.evohub.io/docs-custom-domain.md). ## Password Pick **Password**, then under **Site password** enter 8 to 128 characters and choose **Set password** (or **Change password**). The password is stored hashed and cannot be shown again. Changing it signs every reader out. Repeated wrong passwords are slowed down: after too many attempts in a short time, sign-in is refused for a while. ## EvoHub members Pick **EvoHub members**. By default every active member of your organization can read the site. Under **Who may read**, add **Teams** or **Roles** to narrow it down; a reader needs to be in one of them. Readers sign in with their EvoHub account; the docs site never sees their EvoHub login. A sign-in lasts at most 12 hours, and every visit is checked against EvoHub, so people who are removed from the organization, suspended, signed out of EvoHub everywhere, or no longer in the listed teams or roles lose access within 30 seconds. ## Your own sign-in (JWT) Use this when your readers already sign in to your own app. Your app signs a short-lived token for the reader and sends them to the docs site with it. ### Configure the key Under **Verify tokens from your app**, pick the **Algorithm**: - **HS256 (shared secret)** — **Generate a secret** (shown once; copy it now) or paste your own of 32 characters or more. A new secret signs everyone out. - **RS256 (RSA public key)** or **ES256 (EC P-256 public key)** — paste the public key (RSA of 2048 bits or more, or EC P-256), or give a JWKS address (`https` only; keys are matched by `kid`). Never paste a private key. Optionally set **Issuer (iss)** and **Audience (aud)**; they are checked when set. Set the **Login address** where unauthenticated readers are sent (with `?redirect=`), and the **Logout address** readers land on after signing out of the docs. ### The token - `exp` is required and at most 24 hours ahead. The token is a sign-in link, not a credential: keep it to minutes. `iat` may not be in the future. - `groups` (a list of strings, or comma-separated text) decides which audience pages the reader sees. - Every other string, number or boolean claim becomes a reader variable, such as `{{user.name}}` or `{{user.plan}}`. Up to 30 claims, 500 characters each. - `jti`, when present, makes the token work only once. Send the reader to `/_auth/jwt` on your docs domain with the token: ```js import jwt from "jsonwebtoken"; // After your own login check, sign a short-lived token for this reader. const token = jwt.sign( { sub: user.id, name: user.name, email: user.email, groups: ["customers"], plan: "pro", }, process.env.DOCS_JWT_SECRET, { algorithm: "HS256", expiresIn: "10m" }, ); res.redirect(`https://docs.example.com/_auth/jwt?token=${token}&return=/`); ``` ### Test it Under **Try it**, **Make a test token** creates a token for the saved HS256 settings with the subject, name, email, groups and extra claims you enter, valid for up to 60 minutes. **Verify a token** checks a token your app signed and tells you whether a reader with it would be let in, or why not. ## Page audiences On a private site that signs readers in with JWT or EvoHub members, you can limit a page to some readers. In the page settings, under **Audience**, add groups: - with JWT, the `groups` in the reader's token; - with EvoHub members, team ids and roles such as `org:admin` (the picker offers **Team:** and **Role:** entries). Only readers in one of the groups see the page. For everyone else it does not exist: it is left out of the navigation, search, `llms.txt` and the MCP server, and its address answers "not found". The audience is a page setting, so it goes live when the page is published. In a GitHub repository, set it with the `audience` front matter key: ```yaml --- title: Partner pricing audience: [partners, org:admin] --- ``` > [!WARNING] > On a public site, and for readers who sign in with the shared password, nobody belongs to a group — so a page with an audience is shown to no one. ## Reader variables On JWT sites, `{{user.name}}`, `{{user.email}}` and any other claim are replaced with the signed-in reader's details in the page text. On EvoHub members sites, only `{{user.name}}` is filled in. They are left as written on public sites and in previews. See [Writing pages](https://docs.evohub.io/writing-pages.md#reader-variables). ## Read tokens for AI tools A private site is closed to AI assistants and scripts unless they send a token. Under **Read tokens for AI tools**, give a token a **Name** and, optionally, the groups it reads as, then **Create token**. Copy it when it is shown; it is not shown again. The token reads the site — including its MCP server and the `.md` copies of pages — as a reader in those groups. Send it as `Authorization: Bearer `. The console shows ready-made setup for Claude Code and for MCP configuration files. A site holds up to 10 tokens; revoke one at any time. ## Related - [Connect a custom domain](https://docs.evohub.io/docs-custom-domain.md) - [API references and AI](https://docs.evohub.io/api-references-and-ai.md) - [Writing pages](https://docs.evohub.io/writing-pages.md) --- Source: https://docs.evohub.io/api-references-and-ai.md # API references, search and AI Every published docs site comes with search, Markdown copies of its pages and an MCP server, so people and AI assistants can find and read your documentation. You can add API references from OpenAPI documents, and see how the site is read in analytics. This page covers all of these. ## API references An API reference turns an OpenAPI document into reference pages on the site: an overview with servers and authentication, and one page per operation with its parameters, request and response schemas, and an example `curl` request. ### Add one :::steps ### Upload the document Open **API reference** in the site's menu. Under **Upload OpenAPI**, enter a **Name** (for example "Payments API"), an optional **Address** (derived from the name when empty), choose the file and **Upload**. EvoHub accepts OpenAPI 3.0 and 3.1 as `.json`, `.yaml` or `.yml`, up to 2 MB. ### Check the result The document is validated when you upload it. Problems that stop it are listed; smaller issues are shown as warnings. References to other files or URLs (remote `$ref`s) are not fetched — they show as placeholders with a warning, so keep the document self-contained. ### Publish A new reference starts as a draft. **Publish** puts it on the site at `/
`. Readers see it once the site itself is published. ::: To update a reference, use **Replace file**. Readers keep seeing the published version until you choose **Publish changes**. On a site that requires review, references go live through a change request like pages. A reference's address cannot be the same as a page's address on the same site. In a GitHub repository, put OpenAPI files in the `_openapi/` folder. See [Docs as code with GitHub](https://docs.evohub.io/github-sync.md#links-images-snippets-and-api-references). ## What readers get ### Search Every site has full-text search over its published pages and API operations. Readers open it with the search box, by pressing `/`, or with `Ctrl+K` (`⌘K` on a Mac). Pages limited to an audience appear only for readers who may see them. ### Reading experience Readers get the navigation, an "On this page" outline, previous and next links, a light/dark switch (unless the theme fixes one), copy buttons on code, and, where you have them, version and language switchers. The site's own labels are shown in the reader's language when the site has several. See [Docs overview](https://docs.evohub.io/docs-overview.md#theme). ### Use with AI Every page has a **Use with AI** menu with: - **Copy page** — the page as Markdown, for pasting into an AI tool. - **View as Markdown** — the page as plain Markdown. - **Open in Claude** and **Open in ChatGPT** — start a conversation about the page. - **Connect to Cursor / VS Code / Claude** — the site's MCP server address, with one-click setup for Cursor and VS Code. ## For AI tools These addresses are on the site's own domain (shown in **Settings**, **AI & LLMs** once the domain is active): | Address | What it is | | --- | --- | | `/llms.txt` | An index of every page, following [llmstxt.org](https://llmstxt.org). | | `/llms-full.txt` | The whole site in one Markdown file. | | `/.md` | Any page as Markdown. Requesting a page with `Accept: text/markdown` works too. | | `/mcp` | An MCP server (Streamable HTTP). | Pages marked **Hide from search engines** are left out of `llms.txt` and the sitemap. ### MCP server The MCP server lets an assistant search and read the docs as tools: | Tool | What it does | | --- | --- | | `search_docs` | Full-text search over pages and API operations. | | `get_page` | One page, API reference overview or operation, as Markdown. | | `list_pages` | Every page in reading order, grouped by section, and the API references. | | `list_api_operations` | The operations of the API references, with their ids. | | `get_api_operation` | One operation as Markdown, with a `curl` example. | To connect it, for example in Claude Code: ```bash claude mcp add --transport http acme-docs https://docs.example.com/mcp ``` **Settings**, **AI & LLMs** shows the exact command and configuration for Claude Code, Claude Desktop, Cursor and VS Code. On a private site, AI tools need a read token. See [Private docs](https://docs.evohub.io/private-docs.md#read-tokens-for-ai-tools). ### AI crawlers **Allow AI crawlers** (in **Settings**, **AI & LLMs**) decides what the site's `robots.txt` says to AI crawlers such as GPTBot, ClaudeBot, PerplexityBot, Google-Extended and CCBot. It is on by default. Turning it off asks them to stay away; search engines are not affected. ## Analytics Turn analytics on in **Settings**, **Analytics** with **Count visits, searches and AI readers**. The numbers are in [EvoTrail](https://docs.evohub.io/evotrail-overview.md), on the **Docs** tab (**View in EvoTrail** in the settings goes there). Pick the site, filter by date range, version and language, and download each table as CSV. An old link to the site's **Analytics** screen opens that tab. | Report | What it shows | | --- | --- | | **Overview** | Page views and visitors per day, top pages, referrers, countries, devices, versions and languages. | | **Search** | What readers search for, searches with no results, and which results they click. | | **AI readers** | AI reads and requests from AI assistants and crawlers by kind (Markdown pages, `llms.txt`, MCP and so on), by agent, and the pages they read. | | **Feedback** | Answers to "Was this page helpful?" per page, and the comments. | | **Content gaps** | Searches that found nothing and missing pages readers hit — the pages worth writing next. | How readers are counted: - **AI reads** count each page once per AI reader a day: an assistant or crawler fetching the same page (or `llms-full.txt`) fifty times in a day is one read, while two pages, or two assistants on one page, are two. A reader is the crawler's name when it gives one, otherwise the kind of request. Every MCP tool call is a read. **AI requests** count every fetch. Days from before AI reads were counted show their requests as reads. - No cookies, and no IP address or browser details are stored. A reader is told apart for one day by a hash that changes every day, so visitors are counted per day. - Readers who send Do Not Track or Global Privacy Control, and bots, are not counted. Nothing is counted on previews. - Searches that look like an email address, a long number or a key are never stored. - Choose how long to keep visit and search figures: 30, 90 or 180 days, 1 year or 2 years. Older figures are deleted automatically. You can also add a **GA4 measurement ID** or a **Segment write key**. They load only when filled in, never on previews, and never for readers who send Global Privacy Control. Those tools set cookies, so asking readers for consent is up to you. ### Page feedback With **Show "Was this page helpful?" on every page** on, readers answer **Yes** or **No** at the bottom of each page and can leave a comment. On the site's **Feedback** page, mark comments **Resolve** (or **Reopen**) or delete them. To download them as CSV, use the **Feedback** report on EvoTrail's **Docs** tab. Feedback is kept for two years or until you delete it. ## Related - [Docs overview](https://docs.evohub.io/docs-overview.md) - [Private docs](https://docs.evohub.io/private-docs.md) - [Docs as code with GitHub](https://docs.evohub.io/github-sync.md) --- Source: https://docs.evohub.io/changelog-overview.md # Changelog overview EvoHub Changelog publishes your release notes as a changelog site on your own domain, with an embeddable "What's new" widget, feeds and email updates for your users. You write entries in Markdown in the EvoHub console and publish them when they are ready. The Changelog console lives at [https://evohub.io/changelog-admin](https://evohub.io/changelog-admin). ## Concepts | Concept | What it is | | --- | --- | | **Changelog** | One changelog site: its name, logo, brand colour, domain and settings. An organization can have several, for example one per product. | | **Entry** | One release note: title, summary, Markdown body, version, categories, tags, date and cover image. | | **Category** | A coloured label such as **New**, **Improved**, **Fixed** or **Security**. Readers can filter by category. | | **Subscriber** | An email address that follows the changelog, after confirming. | A changelog can belong to the whole organization or to one team. A team's changelog is visible only to that team and to organization Admins and Owners. ## Who can do what Changelogs have no members or roles of their own. What you can do on every changelog of your organization comes from your organization role: | Permission | Lets you | | --- | --- | | `changelog:site:read` | Open changelogs, entries, drafts, feedback and analytics. | | `changelog:entry:write` | Write and edit entries, upload images, submit drafts for review, import GitHub releases. | | `changelog:entry:publish` | Publish, schedule and unpublish entries; approve or request changes. | | `changelog:site:write` | Create changelogs and manage their settings: brand, categories, domain, widget, subscribers, integrations, review, access. | | `changelog:site:delete` | Delete a whole changelog. | Members have everything except deleting a changelog; Viewers can read. Deleting a changelog is for Admins. To split the work, build a custom role from the **Changelog writer**, **Changelog reviewer** or **Manages changelogs** presets. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). ## Create a changelog :::steps ### Open Changelog In the EvoHub console, open **Changelog**. **Changelogs** lists every changelog you can reach. ### Create it Choose **New changelog**, enter a **Name** and a **Slug**, and select **Create changelog**. The slug is a short identifier (3–63 lowercase letters, digits and hyphens) that is unique across EvoHub. It names the changelog in the console and in the widget script; it is not the address readers open. You can change it later as **Address** in **Settings**, **General**, but changing it breaks links people already have. Every new changelog starts with four categories: **New**, **Improved**, **Fixed** and **Security**. ### Brand it In **Settings**, **Brand**, upload a logo (PNG, JPEG or WebP, up to 2 MB), choose whether to **Show the site name next to the logo**, and pick the brand colour and theme. In **Settings**, **General**, set the description and a **Website** the logo links to. ### Write entries Choose **New entry**. See [Writing entries](https://docs.evohub.io/writing-entries.md). ### Connect a domain A changelog answers only on its own domain. Connect one in **Settings**, **Domain**. See [Changelog settings](https://docs.evohub.io/changelog-settings.md#custom-domain). ### Publish the changelog Open **Settings**. The banner above the tabs says whether the changelog is **Published**; choose **Publish changelog**. Until then, nobody outside your organization can see it, and published entries appear once the changelog goes public. ::: **Take offline** on the same banner makes the changelog private again without deleting anything. ## What readers see On your domain (for example `https://changelog.example.com`), readers get: - A timeline of entries, newest first, with pinned entries on top of the first page. - Filters by category and tag, and search. - A page per entry, with reactions and a **Send feedback** link when those are on. - A **Subscribe** button for email updates, when email is set up. - RSS, Atom and JSON feeds, `llms.txt`, Markdown copies of entries and an MCP server for AI tools. See [Reaching readers](https://docs.evohub.io/reaching-readers.md). ## The console Inside a changelog, the menu has: - **Entries** (the changelog's name) — every entry, filtered by **Drafts**, **In review**, **Scheduled** and **Published**, by category, or by search. - **Categories** — add, rename, recolour, reorder and delete categories (up to 30 per changelog). - **Feedback** — comments readers sent about entries. - **Subscribers** — when to email subscribers, and the list of subscribers. - **Settings** — **General**, **Brand**, **Domain**, **Access**, **Notifications**, **Analytics**, **GitHub**, **Links**, **Widget** and **Review**. How the changelog is read — views, visitors, widget opens and clicks, reactions, feed reads, AI readers, emails and subscriber growth — is in [EvoTrail](https://docs.evohub.io/evotrail-overview.md), under **Changelog**. ## Delete a changelog **Settings**, **General**, **Delete changelog** removes every entry, uploaded image, category and the custom domain. This cannot be undone. Only Admins (or a custom role with `changelog:site:delete`) can do this. ## Billing Changelog is billed by usage, not per seat: any number of people can write, and any number can read. Two things are metered: each hour a changelog is published on its custom domain (0.9 EHU per site-hour, about $9–10 a month), and each email delivered to a subscriber (0.7 EHU). Readers, entries, the widget and feeds cost nothing extra. See [How billing works](https://docs.evohub.io/how-billing-works.md). ## Related - [Writing entries](https://docs.evohub.io/writing-entries.md) - [Reaching readers](https://docs.evohub.io/reaching-readers.md) - [Embed the widget](https://docs.evohub.io/changelog-widget.md) - [Changelog settings](https://docs.evohub.io/changelog-settings.md) - [Managing the changelog from GitHub](https://docs.evohub.io/changelog-github.md) --- Source: https://docs.evohub.io/writing-entries.md # Writing entries An entry is one release note. You write it as a draft, publish it now or schedule it for later, and readers see the published copy. This page covers the entry editor, categories, the publishing workflow, review and importing from GitHub releases. ## Create an entry In a changelog, choose **New entry**, give it a **Title**, and select **Create draft**. Nothing is public until you publish it. ## The entry editor The body is Markdown, written in the same editor as EvoHub Docs pages: a toolbar, an **Insert** menu for callouts, tabs, code groups, diagrams and other components, image upload (paste or drop an image into the editor), and a Markdown cheat sheet. See [Writing pages](https://docs.evohub.io/writing-pages.md) for the full syntax. Beside the body, **Details** holds: | Field | What it does | | --- | --- | | **Summary** | One or two sentences (up to 300 characters), shown in the list, feeds and link previews. | | **Version** | Optional, like `v2.4.0`. | | **Categories** | Up to 5 per entry. | | **Tags** | Free-form labels, up to 10 per entry. Readers can filter by tag. | | **Date shown** | Optional. Leave it empty to show the publish time. | | **Author name** | Leave it empty to show the name of whoever publishes. | | **Cover image** | Shown above the entry and in link previews. | | **Pin to the top** | Pinned entries stay above the timeline on the first page. | | **Address** | The entry's part of the web address, derived from the title. | Images you upload are stored with the changelog: PNG, JPEG, WebP, GIF or SVG, up to 5 MB each. > [!WARNING] > Changing the **Address** of a published entry moves it at once and breaks links people already have. **Save** keeps your edits in the draft. If someone else saved the same entry in the meantime, you choose whose version to keep; nothing is overwritten silently. ## Categories Every changelog starts with **New**, **Improved**, **Fixed** and **Security**. On the **Categories** page, people who manage the changelog can add categories (up to 30), change names and colours, reorder them, and delete them. Deleting a category removes it from every entry and from subscribers who followed it. ## Statuses | Status | Meaning | | --- | --- | | **Draft** | Being written; not public. | | **In review** | Submitted for review. | | **Scheduled** | Will publish itself at a set time. | | **Published** | Live. | A published entry that you edit stays **Published**: readers keep seeing the live copy, and the editor shows **Unpublished changes** until you choose **Publish changes**. ## Publish Anyone with `changelog:entry:publish` can: - **Publish now** — the draft goes live. The first time, it also sends notifications (below). - **Schedule…** — pick a date and time up to a year ahead. The entry goes live by itself at that time; **Unschedule** cancels it. - **Unpublish** — takes the entry off the changelog. The draft stays. ### Notify subscribers and integrations When you publish or schedule, **Notify subscribers and integrations** decides whether the entry is announced: emailed to subscribers (or included in the weekly digest), and posted to Slack and webhooks. Only the first publish of an entry is announced; editing and republishing never sends anything again. See [Reaching readers](https://docs.evohub.io/reaching-readers.md). ## Review In **Settings**, **Review**, turn on **Entries need approval to go live** and set **Approvals needed** (1 to 3). Then: 1. A writer saves the draft and chooses **Submit for review**. 2. Anyone with `changelog:entry:publish` reviews it in the editor's **Review** panel with **Approve** or **Request changes**, and an optional comment. 3. Once the entry has enough current approvals, it can be published or scheduled. The rules: - An approval counts only for the draft it was given on. Any later edit makes earlier approvals stale (they show as **Older draft**), and the entry needs approving again. - The person who edited the draft last cannot approve it. - Reviews must come from a person signed in to EvoHub. An API key cannot approve. - Nobody publishes around the gate, Admins included. The approvals are checked again when a scheduled entry fires; if they no longer hold, the entry goes back to **In review** instead of publishing. ## Publish history Every publish is kept in **Publish history** (the newest 50). **View** shows an earlier version; **Restore** copies it into the draft without publishing it. ## Import GitHub releases To start from release notes you already have, open **Settings**, **GitHub**, find **GitHub releases** and choose **Import releases…**: 1. Enter the **Repository** as `owner/name`. Public repositories only. 2. Choose how many **Latest releases to import** (1 to 50), and whether to **Include pre-releases**. 3. Choose **Import**. Each release becomes a draft entry: the title is the release name (or tag), the version is the tag, the date is the release date, and the body is the release notes. Releases you imported before and draft releases are skipped. Nothing is published, so nothing is announced; review the drafts and publish the ones you want. A changelog can run up to 10 imports an hour. ## Related - [Changelog overview](https://docs.evohub.io/changelog-overview.md) - [Reaching readers](https://docs.evohub.io/reaching-readers.md) - [Writing pages](https://docs.evohub.io/writing-pages.md) --- Source: https://docs.evohub.io/reaching-readers.md # Reaching readers Publishing an entry is half the job; the other half is making sure people see it. A changelog can show new entries inside your own app with a widget, email them to subscribers, post them to Slack and your own webhooks, and offer feeds for readers and AI tools. This page covers each channel. Most channels use the changelog's own domain, so connect one first. See [Changelog settings](https://docs.evohub.io/changelog-settings.md#custom-domain). ## The "What's new" widget The widget adds a button to your website or app that opens the changelog's 10 newest entries in a panel, with a count of entries the visitor has not seen yet. ### Add it to your site :::steps ### Check the settings In **Settings**, **Widget**, keep **Show the widget** on, and set the **Button label** (1 to 30 characters; "What's new" by default) and the **Position**: **Bottom right**, **Bottom left**, or **No button (I'll attach it to my own element)**. Choose **Save changes**. ### Copy the script Under **Add it to your site**, the console shows the script tag with your changelog's domain and slug filled in. It looks like this: ```html ``` ### Paste it into your pages Add the tag to every page where the widget should appear, before the closing `` tag. ::: Optional attributes choose the position (`data-position`), attach the widget to your own button (`data-selector`) and pick the theme (`data-theme`). [Embed the widget](https://docs.evohub.io/changelog-widget.md) covers them, with a React example and the JavaScript API. ### Embed the list in a page Where you cannot add scripts, or to show the list inside a page, use the iframe: ```html ``` Under **Allowed sites**, list the origins allowed to show the iframe, such as `https://app.example.com`. Leave it empty to allow any site. ### Content Security Policy If your site sets a Content Security Policy, allow your changelog's domain in `script-src` and `connect-src`. Add it to `style-src` for older browsers, and to `frame-src` if you use the iframe. The widget sets no cookies. It remembers the latest entries for a minute in the browser's local storage, so page views within a minute do not fetch them again. ## Feeds Every published changelog has three feeds on its domain: | Feed | Address | | --- | --- | | RSS 2.0 | `/feed.xml` | | Atom 1.0 | `/atom.xml` | | JSON Feed 1.1 | `/feed.json` | Add `?category=` to follow one category, for example `https://changelog.example.com/feed.xml?category=fixed`. ## Email subscribers Readers subscribe with the **Subscribe** button on your changelog. They enter their email address, can pick only some categories, and agree to receive emails. Nothing is sent until they confirm through the link in a confirmation email (valid for 7 days). ### Choose when to email On the changelog's **Subscribers** page, under **When to email subscribers**: - **Instant** — email each new entry when it is published. - **Weekly digest** — one email a week with that week's new entries, on the day and hour (UTC) you choose. A week with nothing new sends nothing. - **Off** — no subscribe form and no emails. Only the first publish of an entry is emailed, never an edit, and only when **Notify subscribers and integrations** was on. Subscribers who follow some categories only get entries in those categories. Set a **Reply-to address** if subscribers should be able to reply. The card also shows how many emails were sent today; past the daily limit, emails wait for the next day. Every email carries an unsubscribe link, and mail clients get a one-click unsubscribe. Emails are sent from EvoHub on the changelog's behalf, with your changelog's name and brand colour, and contain no tracking pixels. > [!NOTE] > The subscribe form and every link in the emails open on the changelog's own domain, so nobody can subscribe until the changelog has an active custom domain. ### Manage subscribers The list on the **Subscribers** page shows every address with its status — **Confirmed**, **Waiting to confirm** or **Unsubscribed** — and the categories it follows. Search by email or filter by status. **Export CSV** downloads the list with each address's consent record. Deleting a subscriber erases the address and its consent record. ## Reactions and feedback In **Settings**, **Widget**, under **Reactions and feedback**: - **Reactions** lets readers react to an entry with an emoji: thumbs up, heart, celebrate, eyes or rocket. - **Feedback** adds a **Send feedback** link under each entry. Readers send a short private comment (up to 1,000 characters), with an email address if they want a reply. Read comments on the **Feedback** page, mark them resolved, or delete them. ## Slack and webhooks In **Settings**, **Notifications** (for people who manage the changelog), **Where notifications go** lists up to 10 destinations that announce each newly published entry. Like email, only an entry's first publish with **Notify subscribers and integrations** on is sent. Each destination shows whether it is **Working**, **Paused**, **Not used yet** or whether its **Last delivery failed**. ### Slack and Microsoft Teams Connect Slack or Microsoft Teams once for the whole organization in **Organization → Chat apps** (see [Slack](https://docs.evohub.io/slack.md) and [Microsoft Teams](https://docs.evohub.io/microsoft-teams.md)). Then choose **Add destination**, pick **Slack or Microsoft Teams channel**, choose the **Channel** and select **Add destination**. Each new entry is posted with its title, date, version and summary, and an **Open** button that links to it. Destinations made earlier from a Slack incoming-webhook address show as **Slack webhook** and keep working until you remove them; they post the entry with a **Read the update** button. With Slack connected, **Move to Slack app** moves one to a channel of the Slack app. ### Webhooks Choose **Add destination**, pick **Webhook**, and enter a name and an `https` address. EvoHub shows a signing secret once; copy it then. Each new entry is sent as a `POST` with a JSON body: ```json { "id": "cdel_...", "type": "entry.published", "created_at": "2026-10-01T09:00:00Z", "site": { "slug": "acme", "name": "Acme Updates", "url": "https://changelog.example.com" }, "entry": { "slug": "faster-dashboards", "title": "Faster dashboards", "summary": "Dashboards load twice as fast.", "version": "v2.4.0", "date": "2026-10-01T09:00:00Z", "categories": [{ "slug": "improved", "name": "Improved" }], "tags": ["dashboards"], "url": "https://changelog.example.com/faster-dashboards", "body_md": "..." } } ``` Requests carry the headers `X-EvoHub-Event`, `X-EvoHub-Delivery` and `X-EvoHub-Signature: t=,v1=`. The signature is an HMAC-SHA256 of `t + "." + raw body` with your signing secret. Verify it on the raw body and reject requests more than 5 minutes old: ```js import crypto from "node:crypto"; // rawBody: the request body exactly as received, not re-serialised JSON. export function verify(rawBody, header, secret) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const t = Number(parts.t); if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; const expected = crypto .createHmac("sha256", secret) .update(t + "." + rawBody) .digest("hex"); const a = Buffer.from(expected); const b = Buffer.from(parts.v1 ?? ""); return a.length === b.length && crypto.timingSafeEqual(a, b); } ``` A failed delivery is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. For each destination you can switch it **Active** on or off, **Send test** (a `ping` event), look at the last 50 **Deliveries**, **Rotate secret** (the old secret stops working at once) and **Remove** it. ## AI tools Every published changelog is readable by AI assistants on its domain: - `/llms.txt` and `/llms-full.txt` — an index and the full text, following [llmstxt.org](https://llmstxt.org). - `/.md` — any entry as Markdown (or request the entry with `Accept: text/markdown`). - `/mcp` — an MCP server with the tools `list_entries`, `get_entry` and `search`. **Let AI crawlers read the changelog** in **Settings**, **General** controls what `robots.txt` says to AI crawlers such as GPTBot and ClaudeBot; `llms.txt` stays available either way. ## Related - [Embed the widget](https://docs.evohub.io/changelog-widget.md) - [Writing entries](https://docs.evohub.io/writing-entries.md) - [Changelog settings](https://docs.evohub.io/changelog-settings.md) - [Changelog overview](https://docs.evohub.io/changelog-overview.md) --- Source: https://docs.evohub.io/changelog-settings.md # Changelog settings This page covers the changelog settings that decide where and how it is read: its custom domain, password access, language, and analytics. Settings are under **Settings** in an open changelog. Anyone who can read the changelog can look at them; changing them needs permission to manage changelogs (`changelog:site:write`). ## Custom domain A changelog has exactly one public address: a subdomain you own, such as `changelog.example.com`. EvoHub does not give changelogs an address of its own, so until a domain is active, the changelog is visible only in the console. The subscribe form, email links, the widget and webhook links all use this domain. :::steps ### Enter the hostname Open **Settings**, **Domain**. Under **Your domain**, type the hostname and choose **Connect domain**. Use a subdomain with at least three labels; apex domains (`example.com`) are not supported. ### Add the DNS records The console shows two CNAME records to add at your DNS provider: | Type | Name | Value | | --- | --- | --- | | CNAME | `_acme-challenge.changelog.example.com` | The value shown in the console | | CNAME | `changelog.example.com` | `cname.evohub-dns.com` | Add the `_acme-challenge` record first and wait for it to validate, then add the routing CNAME, so the domain never goes live without a certificate. If your DNS is proxied through Cloudflare, set SSL to **Full** (not Flexible). ### Wait for validation Choose **Re-check** to refresh. When the status shows **Verified · SSL active**, the changelog is live at `https://changelog.example.com`. ::: The statuses are **Not connected**, **Pending DNS**, **Validating**, **Verified · SSL active** and **Action needed**. For **Action needed**, check that both records are still in your DNS exactly as shown and **Re-check**; if it stays red, remove the domain and connect it again. Keep both records for as long as you use the domain. A hostname can serve only one changelog, docs site or status page. **Remove domain** disconnects it; the changelog keeps its entries. ### Preview before connecting a domain You can see the changelog exactly as readers will, before it has a domain or without one. Open the changelog in the console and, at the top of its entries, choose **Preview**. The changelog opens in a new tab, drawn by the same renderer as the live site, with a thin banner on top: *Preview — only your team can see this. Connect a domain to publish.* - Only signed-in members of your organization who can read the changelog can open a preview. The link from the button works once and only for two minutes; after that, choose **Preview** again. - A preview lasts 15 minutes from the moment you ask for it. Links inside it stay within the preview. - Previews are never indexed or cached, are not counted in analytics, and are not billed. - Nothing can be written from a preview: the subscribe form, reactions and feedback are hidden, so no subscriber is added and no email is sent. It shows the published entries, published changelog or not. ## Password access A changelog is public by default. To share it with a limited group — partners, an early-access group — make it private: 1. Open **Settings**, **Access**. 2. Under **Who can read the published changelog**, pick **Password**. 3. In the password card that appears, enter 8 to 128 characters and choose **Set password**, then choose **Save changes**. Until a password is set, the console says **Set a password first.** Readers then enter the shared password once per session. **Session length** sets how long they stay signed in: 1 hour, 8 hours, 24 hours, 3 days, 7 days or 30 days. **Sign everyone out** ends every session now; changing the password or the visibility does the same. After 10 wrong passwords, a reader has to wait up to 15 minutes before trying again. On a private changelog: - Its feeds and `llms.txt` open only for readers signed in with the password, so feed readers and AI tools cannot read them; the widget and the MCP server are off. - Uploaded images stay reachable by their address. - Subscribing and reactions still work for readers who are signed in. Password access is the only private mode for changelogs. EvoHub does not currently offer sign-in with EvoHub accounts or your own sign-in (JWT) for changelogs; docs sites have both. See [Private docs](https://docs.evohub.io/private-docs.md). ## Header links In **Settings**, **Links**, add up to five links (a label of up to 40 characters and an `https://` address) that the public changelog shows in its header — in a menu on phones — and its footer; if your organization has a published docs site, **Add a link to your docs** adds it in one click, and a docs site's **Navigation** offers **Add a link to your changelog** the same way. ## Language In **Settings**, **General**, **Language** sets the language of the changelog's own words — buttons, headings, dates, the widget and the subscribe pages. Your entries are shown as you wrote them. Available languages: English, Deutsch, Français, Español, Italiano, Português, Nederlands, Polski, Türkçe, Русский, 日本語, 한국어, 中文 and العربية (shown right to left). Under **Customise text**, replace single texts of the public changelog in that language — for example the label of the subscribe button. Pick a text from the suggestions, write your version, and choose **Save changes**. Keep placeholders in curly braces as they are. EvoHub does not translate entries; write them in the language your readers use. ## Analytics Turn on **Count how the changelog is read** in **Settings**, **Analytics**, and choose under **Keep the counts for** how long to keep them: 30, 90 or 180 days, 1 year or 2 years. Older daily counts are deleted automatically. The counts themselves are in [EvoTrail](https://docs.evohub.io/evotrail-overview.md), on the **Changelog** tab, with every other product's. Pick the changelog there to see, for a date range, its views and visitors, What's new widget opens and clicks, reactions, feedback, feed reads, AI readers, its entries, the emails sent per day and subscriber growth. Tables can be downloaded as CSV. An old link to the changelog's **Analytics** screen opens that tab. **AI reads** count each entry once per AI reader a day — a crawler fetching the same entry, the list or `llms-full.txt` fifty times in a day is one read — while **AI requests** count every fetch. A reader is the crawler's name when it gives one, otherwise the kind of request; every MCP tool call is a read. Days from before AI reads were counted show their requests as reads. How readers are counted: no cookies, no stored IP address, and a hash that changes daily, so visitors are counted per day. Readers who send Do Not Track or Global Privacy Control are not counted. ## Other settings - **General** — name, address, description, **Website**, **Let AI crawlers read the changelog**, and deleting. See [Changelog overview](https://docs.evohub.io/changelog-overview.md). - **Brand** — logo, **Show the site name next to the logo**, brand colour and theme. - **Review** — required approvals. See [Writing entries](https://docs.evohub.io/writing-entries.md#review). - **Widget** and **Notifications**, and the **Subscribers** page — see [Reaching readers](https://docs.evohub.io/reaching-readers.md). - **GitHub** — connect a repository and import GitHub releases. See [Managing the changelog from GitHub](https://docs.evohub.io/changelog-github.md) and [Writing entries](https://docs.evohub.io/writing-entries.md#import-github-releases). ## Related - [Changelog overview](https://docs.evohub.io/changelog-overview.md) - [Reaching readers](https://docs.evohub.io/reaching-readers.md) - [Connect a custom domain to a docs site](https://docs.evohub.io/docs-custom-domain.md) --- Source: https://docs.evohub.io/changelog-github.md # Managing the changelog from GitHub You can keep a changelog's entries in a GitHub repository, one Markdown file per entry. Merging to the connected branch publishes, deleting a file unpublishes, and, if you choose, changes made in the EvoHub console go back to the repository as pull requests. It works the same way as [GitHub sync for Docs](https://docs.evohub.io/github-sync.md). ## The repository layout EvoHub reads one folder of one branch, `changelog/` unless you choose another (or the repository root). Every Markdown file in it is one entry: ```text changelog/ ├── 2026-10-10-faster-search.md ├── 2026-10-02-dark-mode.md ├── 2026/ subfolders are fine │ └── 2026-01-15-sso.md ├── images/ │ └── search.png linked from entries by relative path ├── _drafts/ │ └── ideas.md skipped: the name starts with _ └── README.md skipped ``` - Every `.md` or `.markdown` file in the folder or its subfolders is an entry. - Skipped: `README.md`, `index.md`, and any file or folder whose name starts with `_` or `.`. Use a `_drafts/` folder for notes that are not entries yet. - A file's **address** (slug) is its `slug` in the front matter, else its file name without the extension and without a leading date: `2026-10-10-faster-search.md` is `/faster-search`. Slugs are unique within a changelog. ### Front matter A YAML block between two `---` lines at the top of the file, then the body in Markdown: ```markdown --- title: Faster search date: 2026-10-10 categories: [Improved] tags: [search, performance] version: 2.4.0 cover: ./images/search.png author: Ada Lovelace summary: Search answers in half the time. pinned: false draft: false notify: false slug: faster-search --- Search now answers in half the time. ![Results](./images/search.png) ``` | Key | What it does | | --- | --- | | `title` | The entry's title. Without it, a first line `# Heading` is the title (and is left out of the body). | | `date` (or `published_at`) | The date shown on the entry. A date in the future **schedules** the entry for that moment. `2026-10-10` means midnight UTC; add a time (`2026-10-10T09:00:00Z`) to be exact. Without a date, the entry shows when it was published. | | `categories` | Category names or slugs, as a list or comma-separated. A category the changelog does not have yet is created. At most 5. | | `tags` | Free tags, as a list or comma-separated. At most 10. | | `version` | The release it belongs to, kept exactly as written. | | `cover` | A cover image: a path relative to the file, or an `https` address. | | `author` | The author name shown on the entry. | | `summary` | One or two sentences for lists, feeds and email. | | `pinned` | `true` keeps the entry at the top of the changelog. | | `draft` | `true` keeps the entry off the changelog. Setting it on a published entry unpublishes it. | | `notify` | `true` emails subscribers and calls your webhooks when the entry is published for the first time. **Off unless the file says so.** | | `slug` | The entry's address. | Unknown keys are ignored and listed as warnings. Quote values that contain a colon: `title: "Search: twice as fast"`. ### Images Link images relative to the file (`./images/search.png`, `../shared/logo.svg`) or from the repository root (`/assets/logo.png`). A sync uploads them to the changelog's files — PNG, JPEG, WebP, GIF or SVG, up to 5 MB each, the same checks as an upload in the editor — and the entry points at the uploaded copy. Images already on the web (`https://…`) are left as they are. ## One branch per changelog Each changelog follows one branch. Keep the same `changelog/` folder on each branch and give each environment its own changelog: | Branch | Changelog | | --- | --- | | `dev` | the changelog on your staging or development domain | | `main` | the production changelog | Work on a branch from `dev`, merge to `dev` to see the entry on the development changelog, then merge `dev` into `main` to publish it in production — the same flow as code. ## Connect a repository You need permission to manage changelogs (`changelog:site:write`), you must be signed in as a person, and you need to be able to install a GitHub app on the repository. :::steps ### Start from the changelog Open the changelog's **Settings**, **GitHub** and choose **Connect GitHub**. ### Install the EvoHub app on GitHub GitHub opens. Install the EvoHub app on your account or organization and give it access to the repository. If your GitHub organization requires an owner's approval, connect again once it is approved. ### Choose what to sync Back in EvoHub, choose the **Repository**, the **Branch** and the **Folder** (`changelog` by default; leave it empty for the repository root). ### Decide about pull requests Turn on **Open a pull request with changes made in EvoHub** if what you publish in the console should go back to the repository. You can change this later. ### Save Choose **Save and sync**. The first sync starts right away. ::: The GitHub tab then shows the repository, branch and folder, the result of the **last sync** (how many entries were created, updated, published, scheduled, sent to review or unpublished) and the **last push**, and any errors or warnings for individual files, with the line they are on. **Sync now** reads every file again. ## What a sync does A sync runs after every push to the connected branch, usually within a minute. - **A new or changed file** updates its entry's draft and publishes it. A future `date` schedules it instead, and `draft: true` keeps it off the changelog. - **A deleted file** unpublishes its entry. The entry stays in EvoHub (delete it there if you want it gone) and is no longer tied to a file. - **A renamed or moved file** keeps its entry, as long as its slug stays the same. - **A file with an error** (no title, a date that is not a date, more than five categories) is skipped and listed with its line number; the rest of the changelog still syncs. - **If the folder is empty or missing**, nothing is unpublished: the sync stops with an error instead. - If a file's slug is already used by another entry, the entry gets the next free one (`faster-search-2`) and the sync lists a warning. - In the console, **GitHub** is shown as whoever created or last changed an entry through a sync. ### With review turned on If the changelog requires review (see [Writing entries](https://docs.evohub.io/writing-entries.md)), a sync never publishes or schedules. New and changed entries go to **In review**, and a reviewer publishes them in EvoHub as usual. Merging to the branch is then "ready for review", not "live". ## Editing in EvoHub You can still edit entries in the console. What happens next depends on **Open a pull request with changes made in EvoHub**: - **Off**: console edits stay in EvoHub. The next time the file changes on GitHub, you are asked which version to keep (see Conflicts). - **On**: publishing, unpublishing and deleting in EvoHub — and scheduled entries going live — write back to the repository. Commits go to a branch named `evohub/changelog-` and a pull request titled "Changelog edits from EvoHub" is opened; later changes join the same pull request while it is open. A new entry gets a file named `/-.md`, an unpublished entry is written with `draft: true`, and a deleted entry's file is deleted. Your connected branch and the repository's default branch are never written to directly. When you merge the pull request, the next sync recognises the files as its own and changes nothing. ## Conflicts If the same entry changed both in EvoHub and in its file on GitHub since they last matched — or its file was deleted after it was edited in EvoHub — nothing is overwritten. The entry is flagged in the editor and listed in the GitHub tab. For each one choose: - **Keep EvoHub's**: the entry stays as it is in EvoHub. With pull requests on, the next push replaces the file. - **Keep GitHub's**: the file replaces the entry's draft on the next sync (and publishes it, unless the changelog requires review). Your EvoHub edits to it are lost. Resolving a conflict needs permission to publish entries. ## Disconnect **Disconnect** in the GitHub tab removes the link between entries and files. The entries stay as they are. If the app is uninstalled or loses access to the repository on GitHub, the connection shows as disconnected and **Reconnect** picks up where it left off. ## Related - [Writing entries](https://docs.evohub.io/writing-entries.md) - [Reaching readers](https://docs.evohub.io/reaching-readers.md) - [Changelog settings](https://docs.evohub.io/changelog-settings.md) - [GitHub sync for Docs](https://docs.evohub.io/github-sync.md) --- Source: https://docs.evohub.io/changelog-widget.md # Embed the What's new widget The "What's new" widget puts your changelog inside your own website or app: a button that opens a panel with the 10 newest entries, and a red badge with the number of entries the visitor has not seen yet. This page shows how to embed it in plain HTML and in a React app, and how to drive it from your own code. ## Before you start - The changelog is **published** and has an active **custom domain**. The widget loads from that domain, so the console shows no snippet until one is connected. See [Changelog settings](https://docs.evohub.io/changelog-settings.md#custom-domain). - In **Settings**, **Widget**, **Show the widget** is on. When it is off, the widget shows nothing. - The changelog is public. On a changelog with password access, the widget is off. The widget's label comes from **Button label** in the same tab (1 to 30 characters; "What's new" by default), and its colour from the changelog's brand colour. ## Add the script :::steps ### Copy the snippet In **Settings**, **Widget**, under **Add it to your site**, copy the **Script** snippet. It has your changelog's domain and slug filled in: ```html ``` ### Paste it into your pages Add the tag to every page where the widget should appear, before the closing `` tag. The script is small, has no dependencies and loads asynchronously. ### Check it Open your page. A button appears in the corner you chose under **Position**. Click it to open the panel. ::: `data-site` must be the changelog's slug, and the script must be loaded from the changelog's own domain. If the slug does not match that changelog, the widget does not appear and the browser console says why. ### Attributes | Attribute | What it does | | --- | --- | | `data-site` | Required. The changelog's slug. | | `data-position` | `bottom-right`, `bottom-left` or `none`. Overrides **Position** in the settings. With `none`, no floating button is drawn: you open the panel yourself (see below). | | `data-selector` | A CSS selector for your own button or link, for example `#whats-new`. See [Use your own button](#use-your-own-button). | | `data-theme` | `light`, `dark` or `auto` (the default, which follows the visitor's system setting). | ## Use your own button To open the widget from a link or button in your own navigation instead of a floating button, point `data-selector` at it: ```html ``` With a selector, the widget: - draws no floating button, - puts the unread badge on the top-right corner of every element the selector matches, - opens the panel below that element when it is clicked (on narrow screens, the panel opens from the bottom of the screen instead). The elements must already be on the page when the script has loaded; the widget looks for them once. ## React example In a single-page app, load the script once and keep the element it attaches to on the page — for example in your app's header: ```tsx import { useEffect } from "react"; const CHANGELOG = "https://changelog.example.com"; const SITE = "acme"; export function WhatsNewButton() { useEffect(() => { // The widget runs once per page; a second copy of the script does nothing. if (document.querySelector(`script[src="${CHANGELOG}/widget.js"]`)) return; const script = document.createElement("script"); script.src = `${CHANGELOG}/widget.js`; script.async = true; script.dataset.site = SITE; script.dataset.selector = "#whats-new"; document.body.appendChild(script); }, []); return ( ); } ``` Render `WhatsNewButton` in a part of the layout that stays mounted, such as the header. If the button is removed from the page and drawn again later, the badge and the click handler do not come back until the page is reloaded; in that case use the JavaScript API below instead. ## The JavaScript API Once the script has loaded, it adds `window.EvoHubChangelog`: | Call | What it does | | --- | --- | | `EvoHubChangelog.open()` | Opens the panel. | | `EvoHubChangelog.close()` | Closes it. | | `EvoHubChangelog.unread()` | Returns how many entries the visitor has not seen. | When it has loaded, and each time the count changes, the widget fires an `evohub-changelog:unread` event on `document`, with the count in `event.detail.count`. Use it to draw a badge of your own. A click anywhere outside the panel closes it, and that includes the click that called `open()`. Stop that click from bubbling up to the document, or the panel closes as soon as it opens: ```html ``` In React, call `event.stopPropagation()` in the `onClick` handler in the same way. ## What the visitor sees - The panel lists the 10 newest entries, pinned entries first, each with its date, version, categories, title and summary. A title opens the entry on your changelog in a new tab. - The footer links to the whole changelog and, when email subscriptions are on, to **Subscribe**. - Entries the visitor has not seen are highlighted and counted in the badge; above 9 it shows "9+". On a visitor's first visit, entries from the last 30 days count as new. Opening the panel marks everything in it as seen. - The panel closes with **Esc**, the close button or a click outside it, and keyboard focus stays inside it while it is open. - The widget's text follows the changelog's **Language** setting. What a visitor has seen is kept in their browser's local storage, so it is per browser, not per person. The widget sets no cookies. It keeps the latest entries in local storage for a minute, so page views within a minute do not fetch them again. When the changelog counts its readers, widget opens and clicks are counted too, except for visitors who send Do Not Track or Global Privacy Control. ## Embed the list in a page Where you cannot add scripts, or to show the list inside a page rather than in a panel, use the iframe from the same **Add it to your site** card: ```html ``` Under **Allowed sites** in **Settings**, **Widget**, list the origins allowed to show the iframe, such as `https://app.example.com` (up to 20; `http://localhost` is accepted for testing). Leave it empty to allow any site. **Allowed sites** applies only to the iframe; the script works on any site. ## Content Security Policy If your site sets a Content Security Policy, allow your changelog's domain: ```text script-src https://changelog.example.com; connect-src https://changelog.example.com; style-src https://changelog.example.com; frame-src https://changelog.example.com ``` `script-src` and `connect-src` are needed for the script. `style-src` is only needed for older browsers that cannot style the widget from the script, and `frame-src` only for the iframe. ## Troubleshooting - **Nothing appears**: check that the changelog is published and public, that **Show the widget** is on, and that the script's address is the changelog's domain and `data-site` is its slug. The browser console shows a warning starting with `[EvoHub Changelog]` when the slug does not match. - **The panel opens and closes at once**: your click handler calls `open()` without stopping the click; see [The JavaScript API](#the-javascript-api). - **No badge on your own button**: the element the selector names was not on the page when the script loaded, or it was drawn again since. - **A new entry does not show yet**: the widget's list can be up to a couple of minutes old in the browser and at the edge. ## Related - [Reaching readers](https://docs.evohub.io/reaching-readers.md) - [Changelog settings](https://docs.evohub.io/changelog-settings.md) - [Changelog overview](https://docs.evohub.io/changelog-overview.md) --- Source: https://docs.evohub.io/catalog-overview.md # Catalog overview EvoHub Catalog keeps your services, their owners and their promises in one place, and checks them against what is actually running. Your teams declare each service once: who owns it, how critical it is, where its runbook lives. An [EvoHub agent](https://docs.evohub.io/agent-overview.md) in each cluster or VM, or a read-only [cloud account](https://docs.evohub.io/catalog-cloud-accounts.md), reports what runs there. Catalog matches the two, and the gaps show up on their own: workloads nobody owns, services declared but not running, and environments that have drifted apart. Catalog lives under **Catalog** in the EvoHub console. ## Declared and observed Everything in Catalog is one of two kinds, and neither side can write the other's: - **Declared**: what people say. Services, their owner team, tier, lifecycle and links, tiers and their SLA targets, and environments. Only people change these. - **Observed**: what agents and cloud accounts report. Resources, applications, images, their history and the map. These are read-only: they change when your infrastructure does. The one place a person decides something about an observation is which service it belongs to. That is what the [match queue](https://docs.evohub.io/catalog-matching-and-drift.md) is for. ## Concepts ### Environment An environment is one place you run software, such as a production cluster, a staging cluster or a group of VMs. You create environments yourself under **Catalog → Environments and agents**, each with a **Name** (for example `eu-prod`), a **Kind** (`prod`, `staging`, `dev` or `other`) and an optional description. Every agent and every cloud account reports into exactly one environment, chosen when you install or connect it. Several agents can report into the same environment, for example one per VM. An environment cannot be deleted while something still reports into it. ### Service A service (called a *component* in the API) is something your teams build and run, declared by a person. It has: | Field | What it is | | --- | --- | | **Name** | How it is shown, for example "Payments API". | | **Slug** | Its identifier, for example `payments-api`: lowercase letters, digits and hyphens. It is used in links and annotations and **cannot change later**. | | **Kind** | Service, library, database, website, job or other. | | **Lifecycle** | Experimental, production, deprecated or retired. | | **Owner team** | The team that answers for it. | | **Tier** | How critical it is, from your organization's [tiers](https://docs.evohub.io/catalog-tiers-and-sla.md). | | **Repository**, **Docs**, **Runbook** | Links, shown on the service's page. They must be `http` or `https` addresses. | A service can also have an optional RACI (responsible, accountable, consulted, informed). See [Tiers and SLA reports](https://docs.evohub.io/catalog-tiers-and-sla.md#responsibilities). Deleting a service removes the declaration only. Its resources stay and show up as unowned. ### Resource A resource is one thing an agent or a cloud account saw: a Deployment, a Service, an Ingress, a Node, a systemd service on a VM, an EC2 instance, a bucket. Each resource has a status: | Status | Meaning | | --- | --- | | **live** | The last sweep saw it. | | **missing** | The last complete sweep did not see it. | | **gone** | It has been missing for a day. | | **stale** | The agent that reports it has stopped reporting, so nobody knows. | A resource that was missing or gone and is seen again is live again, with its history intact. Absence is never taken as deletion straight away: a sweep that could not list everything marks nothing missing. Each resource keeps its history. Open one under **Inventory → Resources** to see its current state, every change the sweeps observed with a before-and-after diff, what it connects to, and, for a workload, how it is running right now. ### Application An application is what runs together. Catalog groups resources the way your cluster already groups them: an Argo CD application, a Helm release or an `app.kubernetes.io/instance` label names one; otherwise each workload is one, together with its Services, the Ingresses routing to them, its autoscaler and its volume claims. On a VM, an application is a Compose project, a container, or a systemd service that is not part of the operating system. Ownership is decided per application: putting an application in a service puts every resource in it there too, now and as resources come and go. Applications are listed under **Inventory → Applications**, by namespace. ### Health From the agent's live reports, each workload, application and VM service has a health: **healthy**, **degraded** (fewer pods ready than wanted, or containers failing), **down** (wants pods, none ready), **idle** (scaled to zero) or **unknown** (no report in the last ten minutes). ### Match queue The match queue holds proposals that an application belongs to a service. A resource carrying the `evohub.io/component` annotation is bound to that service without asking; everything else is only ever proposed, strongest first, for a person to accept or reject. A rejected pair is never proposed again. See [Matching and drift](https://docs.evohub.io/catalog-matching-and-drift.md). ### Drift Drift is where what was declared and what runs disagree: resources nobody owns, services with nothing running, a service running in staging but missing from production, staging and production on different images, resources that disappeared, workloads that are not ready, and cloud machines that open an admin port to the internet. See [Matching and drift](https://docs.evohub.io/catalog-matching-and-drift.md#drift). ### Tier and SLA report Tiers are your organization's own levels of criticality, each with SLA targets: availability, and response and resolution times per On-Call severity. Each service takes its tier's targets and can change any of them for itself. Every month, Catalog measures each service against its targets, from the Uptime monitors and On-Call integrations tied to it. See [Tiers and SLA reports](https://docs.evohub.io/catalog-tiers-and-sla.md). ## The console The Catalog navigation has three sections. Each entry shows only to people whose role can read it. | Section | Page | What it shows | | --- | --- | --- | | Catalog | **Overview** | How everything the agents report is running, what needs attention, how much has an owner, and recent changes. Each number opens the list behind it. | | | **Services** | The declared services, with filters by owner, tier, lifecycle and kind. Waiting match proposals are counted on this entry. | | | **Map** | Applications by namespace, coloured by health, owner or tier. Open one for its tree, down to each pod. Cloud accounts are drawn region by region. | | Inventory | **Applications** | Applications by environment and namespace. | | | **Resources** | Everything observed, with filters for kind, status, namespace, image and unowned resources. | | | **Images** | Every container image in use, most used first. | | Settings | **Environments and agents** | Your environments and which agents report into each. | | | **Tiers and SLAs** | Your tiers and their targets. | **Drift** (**All drift**) and the **Match queue** (the suggestions waiting) open from the Overview. From the Overview you can also export the whole inventory, every asset with its service, owner, tier, SLA and RACI, as CSV or JSON for an audit. Agents and cloud accounts belong to the organization rather than to Catalog, so they are managed under **Organization → Agents** and **Organization → Cloud accounts**. ## Billing Catalog is priced per watched environment: each environment an agent or a cloud account reports from is metered at **11 EvoHub tokens a day, about $5 a month**, however many clusters or machines are in it. Services, agents, cloud accounts, the resources they discover, the map, drift and SLA reports are free. Your organization's free monthly allowance applies. See [How billing works](https://docs.evohub.io/how-billing-works.md). ## Related - [Get started with Catalog](https://docs.evohub.io/catalog-getting-started.md) - [Matching and drift](https://docs.evohub.io/catalog-matching-and-drift.md) - [Tiers and SLA reports](https://docs.evohub.io/catalog-tiers-and-sla.md) - [Cloud accounts](https://docs.evohub.io/catalog-cloud-accounts.md) - [Catalog permissions](https://docs.evohub.io/catalog-permissions.md) - [EvoHub Agent](https://docs.evohub.io/agent-overview.md) - [Catalog API](https://docs.evohub.io/catalog-api.md) --- Source: https://docs.evohub.io/catalog-getting-started.md # Get started with Catalog This page takes you from an empty Catalog to services with owners and resources matched to them. You need permission to create environments and enroll agents; by default that is an Owner or Admin. See [Catalog permissions](https://docs.evohub.io/catalog-permissions.md). ## 1. Create an environment An environment is one place you run software, such as a production cluster. Create one for each cluster, or each group of VMs, that you want to see. :::steps ### Open Environments and agents In the console, open **Catalog**, then **Settings → Environments and agents**, and select **New environment**. ### Name it Enter a **Name** such as `eu-prod`, pick its **Kind** (`prod`, `staging`, `dev` or `other`) and, if you like, a **Description**. ### Create it Select **Create**. The environment shows **No agent reports here yet.** until something reports into it. ::: ## 2. Connect it Catalog sees nothing on its own. Give it an agent, a cloud account, or both, for each environment. ### Install an agent The [EvoHub agent](https://docs.evohub.io/agent-overview.md) runs in your Kubernetes cluster or on a Linux VM, reads what runs there with read-only access, and reports it. Secret values and environment variable values never leave your network. :::steps ### Start from the environment On the environment's card, select **Install an agent**, then **Go to Agents**. This opens **Organization → Agents** with that environment chosen; select **New agent**. Agents are managed there, not in Catalog, because one agent can serve several products. ### Describe the agent Enter a **Name**, choose **Where it runs** (**Kubernetes cluster** or **Linux VM**) and check the **Environment**. ### Create the token Select **Create token**. EvoHub creates a single-use enrollment token, valid for 15 minutes, and shows the install commands with it filled in. ### Run the commands Choose **One command**, or **Keep it out of shell history** to paste the token separately, then copy and run the commands. See [Install on Kubernetes](https://docs.evohub.io/agent-kubernetes.md) or [Install on a VM](https://docs.evohub.io/agent-vm.md) for what they do. ::: The agent enrolls, sends a heartbeat every minute and a full sweep every five minutes. What it found appears in Catalog within a few minutes, and the agent shows as **Reporting** under **Environments and agents** and **Organization → Agents**. An agent that stops reporting shows **Not reporting**, and after ten minutes without a heartbeat what it reported is marked stale. ### Connect a cloud account For AWS, Azure, GCP or portvMind, EvoHub can read the account itself with read-only access you grant, every 15 minutes, with no agent to install. See [Cloud accounts](https://docs.evohub.io/catalog-cloud-accounts.md). ## 3. Declare your services :::steps ### Open Services Open **Catalog → Services** and select **New service** (or **Declare your first service**). ### Fill in what you know Enter the **Name** and the **Slug**. The slug is lowercase letters, digits and hyphens, for example `payments-api`, and cannot change later. Pick the **Kind**, **Lifecycle**, **Owner team** and **Tier**, and add the **Repository**, **Docs** and **Runbook** links. Only the name and slug are required. ### Create it Select **Create service**. Resources that already name this service in their annotation are matched to it at once. ::: ## 4. Match what runs to your services There are two ways a resource joins a service. **Annotate it.** Add the `evohub.io/component` annotation (or label) with the service's slug to your manifests, for example on a Deployment: ```yaml metadata: annotations: evohub.io/component: payments-api ``` The next sweep binds it to that service without asking. Removing the annotation removes the binding at the next sweep. **Review the match queue.** For everything without an annotation, Catalog proposes a service, for example when an application's name or `app.kubernetes.io/name` label is a service's slug. The Overview's **Ownership** panel shows how many suggestions are waiting and opens the **Match queue**, where you accept or reject each proposal. The **Services** entry in the navigation shows the same count. Accepting puts the application, and everything in it, in that service. You can also open any application or resource and choose its service by hand. See [Matching and drift](https://docs.evohub.io/catalog-matching-and-drift.md). ## 5. Tie monitors and integrations to services A service's [SLA report](https://docs.evohub.io/catalog-tiers-and-sla.md#the-sla-report) is measured from what watches it. Set the **Catalog service** field: - on an Uptime monitor, when you create or edit it, so its checks count towards the service's availability - on an On-Call integration, so the alerts it raises count towards the service's response and resolution times ## 6. Look at the gaps With services declared and agents reporting, open the **Overview**. It shows what needs attention, the applications without an owner, and recent changes. **All drift** opens the full [drift report](https://docs.evohub.io/catalog-matching-and-drift.md#drift). ## Related - [Catalog overview](https://docs.evohub.io/catalog-overview.md) - [EvoHub Agent](https://docs.evohub.io/agent-overview.md) - [Cloud accounts](https://docs.evohub.io/catalog-cloud-accounts.md) --- Source: https://docs.evohub.io/catalog-matching-and-drift.md # Matching and drift A resource's service decides who answers for it, so Catalog never guesses silently. This page explains how resources and applications end up in a service, and how the drift report shows where what was declared and what runs disagree. ## How a resource joins a service Each sweep checks every resource it reports, in this order. ### 1. The annotation decides A resource whose `evohub.io/component` annotation (or label) names a service's slug is bound to that service: ```yaml metadata: annotations: evohub.io/component: payments-api ``` - Removing the annotation removes the binding it made, at the next sweep. - An annotation that names a slug nobody has declared proposes creating that service. Accepting the proposal creates it, named after the slug. - When the annotated members of an application all name the same existing service, the whole application is bound to it. When they disagree, each keeps its own and the application is left for a person. ### 2. A person's decision stands A binding a person made is never undone by a sweep. If an annotation later disagrees with it, Catalog proposes the annotation's service instead. **Accept** replaces the person's binding with the manifest's, and from then on the manifest decides. **Reject** keeps the person's binding and ignores the manifest for good. ### 3. Everything else is only proposed Without an annotation, Catalog proposes, never binds, one service per application, when for example: - the application's name, or its first workload's `app.kubernetes.io/name` or `app` label, is a service's slug - its name is what a service's repository link ends in - its workload's `app.kubernetes.io/part-of` label is a service's slug - every bound resource in its namespace belongs to one service - a name or label matches a slug once `-`, `_` and `.` are ignored (`coredns` for `core-dns`), proposed more weakly An application nothing declared fits is proposed as a new service of its own, named after it. The cluster's own namespaces and the EvoHub agent are never proposed. Kubernetes labels can hint at the owner: an `evohub.io/team`, `team` or `owner` label, on the namespace or the application's resources, is shown on the proposal when it names one of your teams. ## The match queue The match queue lists the proposals waiting, strongest first. Open it from the **Ownership** panel on the Catalog **Overview**; the **Services** entry in the navigation shows how many are waiting. For each proposal: - **Accept** puts the application, and everything in it, in that service. A proposal for a service nobody has declared yet creates it, which also needs permission to declare services. - **Reject** means this pair is never proposed again. The confidence a proposal shows only orders the queue; nothing is ever accepted automatically. ## Choosing a service by hand Open an application or a resource and choose its service. An application's choice applies to every resource in it, now and as resources come and go; a resource bound on its own, by its annotation or by hand, keeps its own. Unbinding a resource that carries an `evohub.io/component` annotation lasts only until its next sweep, because the manifest still says so. To move it for good, change the manifest. ## Drift Open **Drift** from the Catalog **Overview** (**All drift**). You can narrow it to one environment. Each section lists at most 100 entries; the [Resources](https://docs.evohub.io/catalog-overview.md#resource) list filters show the rest. | Section | What it lists | | --- | --- | | **Resources without an owner** | Running resources that no service claims, by environment and kind. | | **Declared, not running** | Services with nothing running. Retired services are left out. | | **Different between environments** | Services running in more than one environment, and what one has that another lacks. Compared by kind and name, not namespace, so `payments-prod` and `payments-staging` holding the same Deployment is not a difference. Always across every environment. | | **Different versions between environments** | The same workload of a service running other images in one environment than in another, for example staging on `payments:1.4` and production still on `1.3`. Always across every environment. | | **Vanished** | Resources that are missing or gone, most recent first. Missing means the last sweep did not see it; gone means it has been absent long enough (a day) to count as removed. | | **Not ready** | Workloads with fewer pods ready than they want, or containers failing, from the agents' reports in the last ten minutes. | ### Exposed cloud resources Cloud machines and services whose security groups open an admin port, such as SSH, RDP or a database, to the whole internet are counted under **Exposed** on the Overview, and marked on the cloud account's map. See [Cloud accounts](https://docs.evohub.io/catalog-cloud-accounts.md#what-you-see). ## Images **Inventory → Images** lists every container image your resources run, most used first, with the environments it runs in. Images pinned to `latest`, or with no tag and no digest, are marked as floating: they run whatever the registry held when their pods last started. Select an image to see the resources that run it. ## Related - [Catalog overview](https://docs.evohub.io/catalog-overview.md) - [Get started with Catalog](https://docs.evohub.io/catalog-getting-started.md) - [Catalog API](https://docs.evohub.io/catalog-api.md) --- Source: https://docs.evohub.io/catalog-tiers-and-sla.md # Tiers and SLA reports A tier says how critical a service is and what that promises. Catalog measures each service against its targets every month, from the Uptime monitors and On-Call integrations that watch it. ## Tiers Tiers are your organization's own. Open **Catalog → Settings → Tiers and SLAs**. A new organization starts with **Tier 1** to **Tier 4**; the first tier is the most critical. Select **Add a tier** to add one. Each tier has a **Name**, a description of what belongs in it (for example "Anything a customer sees or pays through"), a **Colour**, and its targets: | Target | What it promises | | --- | --- | | **Availability** | An uptime percentage, such as `99.9`. | | **Acknowledged within** | Minutes to acknowledge an alert, per On-Call severity (critical, high, medium, low, info). | | **Resolved within** | Minutes to resolve an alert, per severity. | | **Support hours** | Around the clock (24x7), or business hours with a timezone, days and times. | | **Owner within** | Days a service may go without an owner team. | Every target is optional. Use the arrows to make a tier **More critical** or **Less critical**; a service's tier number follows the order. Changing a tier's name, description or targets makes a new version of it. Versions are kept, so a past month's SLA report reads the targets that applied then; **Every version of this tier** lists them. Changing only the colour is not a new version. A tier is never deleted. **Archive** takes it out of use: it can no longer be chosen, and the services already in it keep it. An organization can have up to 50 tiers in use. ## A service's SLA Choose a service's **Tier** on its page. The service takes the tier's targets, and its **SLA** section can change any of them for itself: empty fields take the tier's value, shown greyed, and the targets the service changes are marked. A service with no tier has only its own targets. ## Responsibilities The owner team answers for a service. If you need more, add a RACI under **Responsibilities** on the service's page with **Add a RACI**. Each line is a role and a team or a person: | Role | Meaning | | --- | --- | | **Responsible** | Does the work | | **Accountable** | Answers for it; exactly one | | **Consulted** | Asked before a change | | **Informed** | Told after | A RACI is optional, saved as a whole, and must have exactly one Accountable line. It can have up to 50 lines. ## The SLA report Each service's page has an **SLA report** for each of the last twelve calendar months, in UTC. The current month is measured up to now. To be measured, a service needs what watches it to be tied to it: set the **Catalog service** field on its Uptime monitors and on its On-Call integrations. - **Availability** is summed over the Uptime monitors tied to the service, with each monitor's checks, and compared with the availability target. - **Response and resolution** come from the On-Call alerts raised by the integrations and monitors tied to the service, per severity: how many alerts, how many were acknowledged and resolved, the median and 90th percentile times, and how many breached the targets. Each part shows **Met** or **Missed** against its target. A part that has nothing to measure, or cannot be read at the moment, says so without failing the rest of the report. The targets used are the service's tier as it read at the end of the month, with the service's own changes over it. > [!NOTE] > Measurement runs around the clock. Support hours are shown on the report but not applied yet. ## Permissions Reading tiers and SLA reports needs `catalog:component:read`. Creating, editing, reordering and archiving tiers, changing a service's targets and setting its RACI need `catalog:component:write`, which Members hold by default. See [Catalog permissions](https://docs.evohub.io/catalog-permissions.md). ## Related - [Catalog overview](https://docs.evohub.io/catalog-overview.md) - [Uptime monitoring](https://docs.evohub.io/uptime-overview.md) - [Alerts and incidents](https://docs.evohub.io/alerts-and-incidents.md) --- Source: https://docs.evohub.io/catalog-cloud-accounts.md # Cloud accounts A cloud account is read by EvoHub itself, with no agent to install. You grant read-only access in your cloud, EvoHub reads the account every 15 minutes, and what it finds appears in Catalog beside what your agents report: networks, machines, load balancers, databases and more, and who answers for them. Cloud accounts belong to the organization, beside its agents, so they are managed under **Organization → Cloud accounts**. ## Connect an account :::steps ### Start the connection Open **Organization → Cloud accounts** and select **Connect a cloud account**. Pick the provider, enter a **Name** (for example "Production account"), and choose the **Environment** whose inventory the account belongs to. ### Choose regions Under **Regions**, list the regions to read, or leave it empty to read every region the account has enabled. ### Set up access EvoHub creates the connection and shows the steps for your provider (below). Until access is in place the account waits for it. ### Save and check Give what the steps printed and select **Save and check**. EvoHub tries the access at once and shows what the cloud answered. From then on it reads the account every 15 minutes. ::: An organization can connect up to 20 accounts. ### AWS EvoHub assumes a read-only IAM role that you create in the account from a CloudFormation template. 1. **Download the template.** It creates a role (named `EvoHubCatalogReadOnly` by default; the name must start with `EvoHub`) that trusts EvoHub only with this connection's external ID, and allows only the listing calls Catalog makes: no data, no secrets, no changes. 2. **Create the role in the account**, then read its ARN from the stack's `RoleArn` output. 3. **Give its ARN** and select **Save and check**. Catalog reads, among others, EC2 instances, VPCs and subnets, security groups, load balancers and target groups, RDS instances and clusters, EKS clusters, Lambda functions and S3 buckets. A Lambda function's environment variables are kept by name only, never their values, and an instance's user data is never read. A service the role does not allow is shown on the connection as not readable; it is not a failure. ### Azure EvoHub reads with the built-in **Reader** role, through its own application. 1. **Run one command in Azure Cloud Shell for each subscription.** The console shows it. It gives EvoHub's application read access and marks the subscription for this connection with the `evohub-connection` tag. 2. **Give the directory (tenant) ID it printed** and select **Save and check**. EvoHub reads only the subscriptions marked for this connection. ### GCP EvoHub reads with **Cloud Asset Viewer** and **Browser**, as its own service account. 1. **Run one command in Cloud Shell for each project.** The console shows it. It gives EvoHub read access and marks the project for this connection with the `evohub-connection` label. 2. Select **Save and check**. EvoHub reads only the projects marked for this connection, and lists the projects it read on the connection. ### portvMind portvMind has no read-only role, so EvoHub reads with an application credential you create. 1. **Run the command the console shows in a terminal**, with your portvMind user name and project ID. It creates an application credential for EvoHub. The credential is restricted, so it cannot create other credentials. 2. **Give the credential it printed**: its **Credential id** and **Credential secret**, and select **Save and check**. EvoHub only lists with the credential. The secret is kept encrypted and is never shown again; to replace it, type a new one. ## What you see What an account reports appears in Catalog like anything an agent reports: under **Inventory → Resources**, matched to services, in [drift](https://docs.evohub.io/catalog-matching-and-drift.md#drift), and counted in the Overview. On **Catalog → Map**, **Cloud accounts** lays each account out region by region: each network with its internet gateway, peerings and subnets (public or private, and the NAT gateway a private one leaves through), what runs in each subnet, what spans the network, and what sits outside any network. Links show traffic, egress, internet access and which security groups let which in. A machine whose security groups open an admin port, such as SSH, RDP or a database, to the whole internet is flagged, and counted under **Exposed** on the Overview. When a machine is also seen by an agent, for example a VM running the agent or a Kubernetes node, the two are shown as the same machine. ## Pause, change or disconnect - **Pause** stops reading the account; **Resume** reads it again at once and then every 15 minutes. A changed role is also read at once. Neither reads again within two minutes of the last read. - You can rename a connection, change its role, directory or regions, or give a new portvMind credential. - **Disconnect** stops reading the account. What it reported stays in Catalog. Remove the role, application or credential in your cloud as well. An account that has not been read for 45 minutes has its resources marked stale. ## Permissions | To | You need | | --- | --- | | See cloud accounts | `identity:cloudaccount:read` | | Connect an account | `identity:cloudaccount:write` and `catalog:source:write` | | Change, check, pause, resume or disconnect one | `identity:cloudaccount:write` | | See an account's architecture on the map | `identity:cloudaccount:read` and `catalog:resource:read` | By default every role can see cloud accounts, and Owners and Admins can connect them. The external ID that the AWS role trusts is shown only to people who can connect accounts. See [Catalog permissions](https://docs.evohub.io/catalog-permissions.md). ## Billing The environment an account reports into is billed as a watched environment, like one an agent reports from, at 11 EvoHub tokens a day. Cloud accounts themselves are free. See [Catalog overview](https://docs.evohub.io/catalog-overview.md#billing). ## Related - [Catalog overview](https://docs.evohub.io/catalog-overview.md) - [EvoHub Agent](https://docs.evohub.io/agent-overview.md) - [Cloud accounts API](https://docs.evohub.io/catalog-api.md#cloud-accounts) --- Source: https://docs.evohub.io/catalog-permissions.md # Catalog permissions Each part of Catalog follows its own permission: who may read and change the declared services, who may read what agents and cloud accounts observed, and who may manage environments. Agents and cloud accounts belong to the organization, so their permissions are the organization's. The same identifiers are the scopes of an [API key](https://docs.evohub.io/api-keys-and-scopes.md). ## The permissions | Permission | Covers | | --- | --- | | `catalog:component:read` | Services, their tiers, SLA and RACI, the tiers and their versions, and SLA reports. | | `catalog:component:write` | Declaring, changing and deleting services, setting their RACI and SLA overrides, and creating, editing, reordering and archiving tiers. | | `catalog:resource:read` | What agents and cloud accounts observed: resources, their history and changes, applications, namespaces, images and the map. | | `catalog:resource:write` | Deciding which service a resource or an application belongs to: accepting and rejecting match proposals, and choosing a service by hand. It never creates or edits a resource. | | `catalog:source:read` | Environments, and the agents and cloud accounts reporting into them. | | `catalog:source:write` | Creating, changing and deleting environments, and renaming, pausing and resuming a source. Also needed to enroll an agent or connect a cloud account, because that decides which environment its inventory lands in. | | `identity:agent:read` | Seeing the organization's agents under **Organization → Agents**. | | `identity:agent:write` | Creating enrollment tokens, renaming and revoking agents. | | `identity:cloudaccount:read` | Seeing the organization's cloud accounts. | | `identity:cloudaccount:write` | Connecting, changing, checking, pausing and disconnecting cloud accounts. | Some pages combine two halves and need both: | To | You need | | --- | --- | | See the match queue | `catalog:component:read` and `catalog:resource:read` | | See drift, or export the inventory | `catalog:component:read` and `catalog:resource:read` | | See a service's resources | `catalog:component:read` and `catalog:resource:read` | | Accept a proposal that creates a new service | `catalog:resource:write` and `catalog:component:write` | | Enroll an agent | `identity:agent:write` and `catalog:source:write` | | Connect a cloud account | `identity:cloudaccount:write` and `catalog:source:write` | | See a cloud account's architecture | `identity:cloudaccount:read` and `catalog:resource:read` | A service's page needs only `catalog:component:read`. Without `catalog:resource:read`, what the agents found is left out of it rather than refused, and the page says which permission would show it. There is deliberately no permission to send inventory. Only an enrolled agent, signing each request with its own key, can report into Catalog; an API key or a person never can. ## Default roles | | Viewer | Member | Admin | | --- | --- | --- | --- | | See services, tiers, SLA reports, resources, the map, drift, environments, agents and cloud accounts | Yes | Yes | Yes | | Declare and change services, their RACI and tiers | — | Yes | Yes | | Accept and reject matches, choose a service by hand | — | — | Yes | | Manage environments | — | — | Yes | | Enroll and revoke agents, connect cloud accounts | — | — | Yes | Owners and Admins acting in person pass every check. API keys are held to their scopes, even when an administrator created them. ## Ready-made Catalog roles The role editor (**Organization → Roles**) offers three levels for Catalog: | Level | Can | Cannot | | --- | --- | --- | | **Viewer** | See services, what the agents and cloud accounts found, environments, the map and drift. | Declare or change a service, accept a match, install an agent or connect a cloud account. | | **Service Owner** | Everything a Viewer sees, plus declare services: owner, tier, runbook and who is responsible. | Accept a match, change environments, install an agent or connect a cloud account. | | **Catalog Admin** | Everything: services, matches, environments, installing agents and connecting cloud accounts. | Manage the organization's members, roles or billing. | See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) for building custom roles. ## Related - [Catalog overview](https://docs.evohub.io/catalog-overview.md) - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) - [Catalog API](https://docs.evohub.io/catalog-api.md) --- Source: https://docs.evohub.io/agent-overview.md # EvoHub Agent overview The EvoHub agent is software your organization runs in its own infrastructure. It lists what is running there, read-only, and reports it to [EvoHub Catalog](https://docs.evohub.io/catalog-overview.md). It runs in two places: - **In a Kubernetes cluster**, installed with Helm. It describes the cluster's workloads, services and how they connect. See [Install on Kubernetes](https://docs.evohub.io/agent-kubernetes.md). - **On a Linux VM**, installed under systemd. It describes that machine: its services, containers and listening ports. See [Install on a VM](https://docs.evohub.io/agent-vm.md). Secret values and environment variable values are removed inside your network, before anything is sent. What the agent reads, sends and never sends is in [What the agent sends](https://docs.evohub.io/agent-data-and-redaction.md). ## Enrollment An agent joins your organization with an **enrollment token**: :::steps ### Create the token In the console, open **Organization → Agents** and select **New agent**. Give it a **Name**, choose **Where it runs** (**Kubernetes cluster** or **Linux VM**) and the **Environment** it reports to, then select **Create token**. ### Install the agent with it The console shows the install commands with the token filled in. The token works **once** and is valid for **15 minutes**. If it expires, create another. ### The agent enrolls On first start the agent generates its own key pair, saves the private key where it runs, and sends EvoHub its public key with the token. Once enrolled, the token is spent and never sent again. ::: Each agent reports to exactly one environment, the one chosen when its token was created. The environment never travels in the agent's requests, so an agent cannot report into an environment it was not enrolled for. ## Signed requests The agent holds no password and no API key. Every request after enrollment is signed with the agent's private key, which never leaves your cluster or VM. The signature covers: - the agent's ID - the method, the path and the query - the SHA-256 of the body - a timestamp - a random nonce EvoHub refuses a nonce it has already seen and a timestamp more than five minutes off, so a request copied in transit cannot be replayed. Nothing the agent sends after enrollment can be reused by someone who sees it. An agent can only send inventory about its own environment. It cannot read anything from EvoHub, and nothing else can send inventory: there is no permission or API key that can. ## Network - **Outbound:** HTTPS on TCP 443 to `api.evohub.io`; in Kubernetes, your cluster's API server; and DNS. - **Inbound:** none. The agent listens on no port. - **Proxies:** the agent honours `HTTPS_PROXY` and `NO_PROXY`. It trusts the public CA roots only, so a proxy that re-signs TLS with a private CA is not supported. A proxy that terminates TLS can read what the agent sends, including the enrollment token while it enrolls. - **Redirects** are never followed. A redirect is logged as an error. ## How often it reports | Report | Default | | --- | --- | | Heartbeat | Every minute | | Full sweep of everything it can list | Every 5 minutes | | Live state: how each workload or service is running now | Every minute | When a sweep could not list something, it says so, and EvoHub marks nothing missing from that sweep. If an agent stops sending heartbeats for ten minutes, what it reported is shown as stale rather than missing. ## Managing agents **Organization → Agents** lists every agent with where it runs, what it is used for, when it was last seen, and whether it is **Reporting**, **Not reporting** or **Revoked**. The agent's version, which it reports on every heartbeat, is kept too. - **Rename agent** changes how it is shown. The agent itself keeps running as it is. - **Revoke agent** stops EvoHub accepting anything from it. Uninstalling an agent does not revoke it; revoke it as well. - To pause an agent's reports without revoking it, pause its source under **Catalog → Environments and agents**. Seeing agents needs `identity:agent:read`; creating tokens, renaming and revoking need `identity:agent:write`, and enrolling also needs `catalog:source:write`. By default Owners and Admins can enroll agents. See [Catalog permissions](https://docs.evohub.io/catalog-permissions.md). ## When EvoHub refuses the agent When EvoHub refuses a request, the agent's log entry carries an `action` field saying what to do, for example `upgrade it`, `resume it there` or `fix its time synchronisation`. After a refusal the agent waits for its next cycle, and waits longer for each refusal in a row, up to an hour. EvoHub supports the current agent protocol and the one before it. An agent too old for EvoHub logs `this agent is too old for EvoHub: upgrade it`. ## Releases Agent versions are EvoHub's release versions, `YY.MM.N`: `26.10.1` is October 2026's first release. No `latest` image or chart is published, and the agent never upgrades itself, so the agent you run changes only when you upgrade it. Images, charts and binaries are signed with Sigstore; see each install page for how to verify them. ## Billing Agents are free. Catalog is billed per watched environment. See [Catalog overview](https://docs.evohub.io/catalog-overview.md#billing). ## Related - [Install on Kubernetes](https://docs.evohub.io/agent-kubernetes.md) - [Install on a VM](https://docs.evohub.io/agent-vm.md) - [What the agent sends](https://docs.evohub.io/agent-data-and-redaction.md) - [Get started with Catalog](https://docs.evohub.io/catalog-getting-started.md) --- Source: https://docs.evohub.io/agent-kubernetes.md # Install the agent on Kubernetes In a Kubernetes cluster the agent is installed with one Helm chart. It describes the cluster's workloads, services and how they connect, and reports them to one Catalog environment. Before you start, create the environment in Catalog and an enrollment token for it; see [Enrollment](https://docs.evohub.io/agent-overview.md#enrollment). ## Install The console's **New agent** dialog shows these commands with your token, version and agent name filled in. ### Keep the token out of shell history Paste the token into a Secret, then point the chart at it: ```bash kubectl create namespace evohub kubectl -n evohub create secret generic evohub-agent-enrollment --from-file=token=/dev/stdin # paste the token, press Enter, then Ctrl-D helm install evohub-agent oci://ghcr.io/evolutionarysynchronization/charts/evohub-agent \ --version 26.10.1 -n evohub --set enrollment.existingSecret=evohub-agent-enrollment ``` ### Or in one command ```bash helm install evohub-agent oci://ghcr.io/evolutionarysynchronization/charts/evohub-agent \ --version 26.10.1 -n evohub --create-namespace --set enrollment.token=evohub_enroll_... ``` The token then also lands in your shell history and in Helm's release record (a `sh.helm.release.v1.*` Secret in the namespace). It is single-use and spent once the agent enrolls. Always pass `--version`. No `latest` chart or image is published, and each chart version pins its image by digest. To set how the agent is shown in EvoHub, add `--set agentName=`; without it, EvoHub shows the pod's hostname. ### Check that it runs ```bash kubectl -n evohub logs deploy/evohub-agent-catalog ``` The agent logs `enrolled`, then its sweeps. What it found appears in Catalog within a few minutes, and the agent shows as **Reporting** under **Organization → Agents**. If EvoHub refuses it, the log entry's `action` field says what to do. The names on this page assume the release name `evohub-agent`. With another release name, the ServiceAccount, Secret and Deployment names change accordingly. ## Verify a release Images and charts are signed with Sigstore, keyless, by the agent's release workflow. Images also carry an SBOM and build provenance. ```bash cosign verify ghcr.io/evolutionarysynchronization/evohub-agent:26.10.1 \ --certificate-identity-regexp '^https://github.com/EvolutionarySynchronization/evohub-agent/\.github/workflows/release\.yml@refs/tags/v' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com ``` ## What it is allowed to do The chart creates one ServiceAccount, `evohub-agent-catalog`, with exactly these grants: | Grant | On | Why | | --- | --- | --- | | `get`, `list`, cluster-wide | `namespaces`, `nodes`, `services`, `persistentvolumeclaims`, `deployments`, `statefulsets`, `daemonsets`, `cronjobs`, `ingresses`, `horizontalpodautoscalers` | What it inventories | | `list`, cluster-wide, only while live state is on (the default) | `pods` | How many run and are ready, which node each is on, and why one fails | | `get`, `update`, in its own namespace | One Secret, by name: `evohub-agent-catalog-identity` | Where it keeps its own key | That is all: - **No access to your Secrets.** It cannot list Secrets, read any Secret other than its own, or create one. - No access to ConfigMaps, and no `watch`: it lists in full sweeps. - No write access to anything except its own Secret. - The ServiceAccount token is mounted into the agent container only. Containers you add with `extraContainers` share the pod and its network, but not the token. Reading those kinds means the agent reads each object in full, in memory, inside your cluster, including container environment variable values. Those values are removed before anything is sent and are never written anywhere. See [What the agent sends](https://docs.evohub.io/agent-data-and-redaction.md). The pod runs as UID 65532, non-root, with a read-only root filesystem, all capabilities dropped, no privilege escalation and the `RuntimeDefault` seccomp profile. The image is distroless and has no shell. If your platform assigns UIDs itself, for example OpenShift, override `podSecurityContext`. ## What it keeps in your cluster - `Secret/evohub-agent-catalog-identity` holds the agent's private key and ID. It is protected the way your Secrets are, including by etcd encryption if you have it. Do not delete it: it is the agent's identity. - `Secret/evohub-agent-enrollment` holds the spent enrollment token, if you created it. You can delete it after the first start. The agent keeps one hash per object between sweeps, and lists objects 100 at a time. The chart sets a 256 MiB memory limit. ## Chart values | Value | Default | | | --- | --- | --- | | `apiUrl` | `https://api.evohub.io` | EvoHub's API, HTTPS only | | `enrollment.token` | empty | The single-use token, if you pass it inline | | `enrollment.existingSecret` | empty | A Secret holding the token, under `enrollment.existingSecretKey` (`token`) | | `agentName` | the pod's hostname | How the agent appears in EvoHub | | `catalog.sweepInterval` | `5m` | How often to run a full sweep | | `catalog.heartbeatInterval` | `1m` | How often to send a heartbeat | | `catalog.liveState.enabled` | `true` | Report how workloads are running now; needs the `pods` grant, which is created only while this is on | | `catalog.liveState.interval` | `1m` | How often to report live state | | `catalog.resources` | requests 20m CPU, 64Mi; limits 500m CPU, 256Mi | The agent container's resources | | `extraEnv` | `[]` | Extra environment, for example `HTTPS_PROXY` and `NO_PROXY` | | `image.digest` | empty | Pin the image by digest as well | | `podSecurityContext`, `securityContext` | non-root, read-only, all capabilities dropped | Override only if your platform requires it | | `nodeSelector`, `tolerations`, `affinity`, `priorityClassName`, `podAnnotations`, `podLabels`, `imagePullSecrets` | empty | The usual scheduling and metadata settings | | `extraContainers` | `[]` | Sidecars your platform requires; the agent needs none | ## Upgrade ```bash helm upgrade evohub-agent oci://ghcr.io/evolutionarysynchronization/charts/evohub-agent \ --version -n evohub --reset-then-reuse-values ``` - **Review the RBAC first.** An upgrade can change what the agent may read. Check with `helm template` or the `helm diff` plugin before applying. - **Its key and ID are kept.** Never use `helm upgrade --force`: it replaces the identity Secret and the agent loses its key. - The agent never upgrades itself, and reports its version on every heartbeat. ## Uninstall ```bash helm uninstall evohub-agent -n evohub ``` This removes everything the chart created, including the agent's key and the cluster-wide ClusterRole and ClusterRoleBinding. It leaves the namespace and any Secret you created yourself. Then revoke the agent under **Organization → Agents**; until you do, it stays enrolled. The resources it reported stay in Catalog and are shown as stale. Installing again enrolls a new agent. ## Outside the cluster The same binary can describe a Kubernetes cluster from a VM or bastion host under systemd, given a kubeconfig. Then its permissions are whatever the kubeconfig allows: give it a read-only ServiceAccount token bound to the same ClusterRole as the chart. Exec-plugin authentication, such as `aws` or `gcloud`, does not work in the unit's sandbox. To describe the VM itself instead, see [Install on a VM](https://docs.evohub.io/agent-vm.md). ## Related - [EvoHub Agent overview](https://docs.evohub.io/agent-overview.md) - [What the agent sends](https://docs.evohub.io/agent-data-and-redaction.md) - [Get started with Catalog](https://docs.evohub.io/catalog-getting-started.md) --- Source: https://docs.evohub.io/agent-vm.md # Install the agent on a VM On a Linux VM the agent runs under systemd and describes the machine it runs on: the host, its systemd services, its Docker containers, its listening ports and, if you turn it on, its installed packages. This is host mode, `EVOHUB_PLATFORM=vm`, which the unit file sets for you. Before you start, create the environment in Catalog and an enrollment token for it, choosing **Linux VM** under **Where it runs**; see [Enrollment](https://docs.evohub.io/agent-overview.md#enrollment). One environment can hold many VMs, each with its own agent, and it is billed once. ## Install The agent is published as static binaries for `linux/amd64` and `linux/arm64`, with its systemd unit, beside the image on `ghcr.io`, one tag per file. Fetching them needs only `curl` and `sha256sum`. The console's **New agent** dialog shows the complete install for your version, with the token and the agent's name filled in, as **One command** or as **Keep it out of shell history**. It runs these steps: :::steps ### Fetch the binary and the unit Download the release's binary for the machine's architecture and the `evohub-agent-host.service` unit, each checked against the digest its manifest names. ### Install the binary and create the agent's user ```bash sudo install -m 0755 evohub-agent /usr/local/bin/evohub-agent sudo useradd --system --home-dir /nonexistent --shell /usr/sbin/nologin evohub-agent ``` ### Store the enrollment token ```bash sudo install -d -m 0700 -o evohub-agent /etc/evohub-agent sudo install -m 0600 -o evohub-agent /dev/stdin /etc/evohub-agent/enrollment-token # paste the token, press Enter, then Ctrl-D ``` ### Install and start the unit ```bash sudo cp evohub-agent-host.service /etc/systemd/system/ sudo systemctl daemon-reload && sudo systemctl enable --now evohub-agent-host ``` ### Check it and remove the token ```bash journalctl -u evohub-agent-host -f ``` Once the agent logs `enrolled`, delete the token file: `sudo rm /etc/evohub-agent/enrollment-token`. ::: What the machine runs appears in Catalog within a few minutes, under the provider `host`, and the agent shows as **Reporting** under **Organization → Agents**. ### Verify a release The fetch checks that each file is byte for byte the one its manifest names. That proves integrity, not origin. To know the file is EvoHub's, verify the manifest's Sigstore signature by the digest the fetch printed, not by the tag: ```bash cosign verify ghcr.io/evolutionarysynchronization/evohub-agent@sha256: \ --certificate-identity-regexp '^https://github.com/EvolutionarySynchronization/evohub-agent/\.github/workflows/release\.yml@refs/tags/v' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com ``` The binaries' `SHA256SUMS` is signed the same way (`SHA256SUMS.sigstore.json`, checked with `cosign verify-blob --bundle`). ## Settings Settings go in a drop-in for the unit (`sudo systemctl edit evohub-agent-host`), under `[Service]`: ```ini [Service] Environment=EVOHUB_AGENT_NAME=web-01 Environment=EVOHUB_HOST_PACKAGES=true ``` | Variable | Default | | | --- | --- | --- | | `EVOHUB_AGENT_NAME` | the hostname | How the agent appears in EvoHub | | `EVOHUB_API_URL` | `https://api.evohub.io` | EvoHub's API, scheme and host only | | `EVOHUB_HOST_PACKAGES` | `false` | Report installed packages (`dpkg-query` or `rpm`) | | `EVOHUB_DOCKER_SOCKET` | `/var/run/docker.sock` | Where to list containers; empty turns containers off | | `EVOHUB_SWEEP_INTERVAL` | `5m` | How often to run a full sweep, 1m to 24h | | `EVOHUB_HEARTBEAT_INTERVAL` | `1m` | How often to send a heartbeat, 10s to 3m | | `HTTPS_PROXY`, `NO_PROXY` | none | An egress proxy | Restart the unit after changing them: `sudo systemctl restart evohub-agent-host`. ### Docker containers The agent lists Docker containers only if it can reach Docker's socket. Add `SupplementaryGroups=docker` to the drop-in to allow it. > [!WARNING] > Membership of the `docker` group is equivalent to root on that machine: whoever controls the agent could then start a privileged container, and the unit's sandbox no longer protects the machine from it. The agent only lists containers. If that matters to you, put a read-only proxy for Docker's API (one that allows `GET /containers/json` only) in front of the socket and point `EVOHUB_DOCKER_SOCKET` at it. ## How it runs The unit runs the agent as its own system user, `evohub-agent`, with a read-only view of the system: the only writable place is its private state directory, where it keeps its key (`/var/lib/evohub-agent/identity.json`, mode 0600). It needs outbound HTTPS and nothing else. It sees which user owns each listening socket, not which process, so a port is put down to the one service running as that user, and left unattributed when that user is root or runs several services. Running the agent as root would name every port's process; that is your decision, not the unit's default. ## What it reads and sends | It reads | For | | --- | --- | | `/etc/machine-id` | The host's identity; every resource is named `host:////` | | `/etc/os-release`, `/proc/sys/kernel`, `/proc/cpuinfo`, `/proc/meminfo`, `/proc/stat`, `/proc/mounts` | What the machine is | | `/sys/class/dmi/id/sys_vendor`, `/var/lib/cloud/data/instance-id`, `/sys/class/dmi/id/board_asset_tag` | Which cloud, and its instance ID; no metadata endpoint is called | | `systemctl list-units`, `list-unit-files`, `show` | Its services | | `/proc/net/tcp`, `tcp6`, `udp`, `udp6`, `/etc/passwd` (user names and IDs only) | What listens, and as which user | | `/proc//fd`, `comm`, `cgroup` | Which process holds a socket, where the agent may read it (as root) | | Docker's socket, only if you allow it | Its containers | | `dpkg-query` or `rpm`, only with `EVOHUB_HOST_PACKAGES=true` | Installed packages | It sends: - **Host**: hostname, distribution, kernel, architecture, CPU count, memory, each local disk's mount point, filesystem and size (a network mount's server name, not its path), IP addresses, boot time, cloud and instance ID. - **Service**, a systemd unit that is enabled, running, starting or failed: its unit file's path, whether it is enabled, its type, its description, the path of the binary it starts and the user it runs as. Never its arguments, its environment or its drop-ins. - **Container**: its name, image and image digest, published ports, and the Compose project and service it belongs to. No command, environment, mounts or other labels. - **ListeningPort**, one per protocol and port: the addresses it is bound to, and the service, container or process name that listens, when the agent can tell. Never a command line. - **Packages**, if turned on: each installed package's name, version and architecture. - **Live state**, every minute: whether the host, each service and each container runs as it should, and the name of what is wrong (`Failed`, `Restarting`, `Unhealthy`, `Error`). It never reads or sends file contents, logs, shell history, anything from a home directory, any process's environment variables or command-line arguments, or what a service or container is doing (connections, traffic, metrics). Every document goes through the same [redaction](https://docs.evohub.io/agent-data-and-redaction.md#redaction) as a Kubernetes object before it is hashed or sent. In Catalog, each container and non-system service is an application of its own (a Compose project is one application), a port is linked to what listens on it, and services and containers are shown running on their host. ## Upgrade The agent never upgrades itself. To upgrade, fetch the new version's binary as in the install, replace `/usr/local/bin/evohub-agent` with it, and restart the unit: ```bash sudo install -m 0755 evohub-agent /usr/local/bin/evohub-agent sudo systemctl restart evohub-agent-host ``` Its key stays in its state directory, so it keeps its identity and history. ## Uninstall ```bash sudo systemctl disable --now evohub-agent-host sudo rm /etc/systemd/system/evohub-agent-host.service sudo rm -rf /etc/systemd/system/evohub-agent-host.service.d /etc/evohub-agent /var/lib/evohub-agent sudo rm /usr/local/bin/evohub-agent sudo systemctl daemon-reload ``` Removing `/var/lib/evohub-agent` deletes the agent's key. Then revoke the agent under **Organization → Agents**; until you do, it stays enrolled. What it reported stays in Catalog and is shown as stale. ## Related - [EvoHub Agent overview](https://docs.evohub.io/agent-overview.md) - [What the agent sends](https://docs.evohub.io/agent-data-and-redaction.md) - [Install on Kubernetes](https://docs.evohub.io/agent-kubernetes.md) --- Source: https://docs.evohub.io/agent-data-and-redaction.md # What the agent sends This page is the contract for what leaves your cluster: what the agent sends, how it removes secrets before sending, and what it never sends. It is written for the people who approve installing it. What the agent sends from a VM is on [Install on a VM](https://docs.evohub.io/agent-vm.md#what-it-reads-and-sends). If the agent ever does something not described here, that is a bug; please [report it](https://docs.evohub.io/get-help.md). ## Requests | Request | When | Body | | --- | --- | --- | | `POST /api/v1/agent-enroll` | Until enrolled | The public key, the hostname (the pod's name, or the VM's under systemd), the agent's version and platform, and its name if you set one | | `POST /api/v1/catalog/ingest/heartbeat` | Every minute | Its version and uptime | | `POST /api/v1/catalog/ingest/sweeps` | At the start of each sweep, every 5 minutes | Empty | | `POST /api/v1/catalog/ingest/sweeps/{id}/resources` | Per batch of up to 1000 objects | The objects, below | | `POST /api/v1/catalog/ingest/sweeps/{id}/complete` | At the end of a sweep | Whether it is complete, and the kinds it could not list | | `POST /api/v1/catalog/ingest/live` | Every minute | Live state, below | Every request carries `User-Agent: evohub-agent/`. Every request after enrollment is [signed](https://docs.evohub.io/agent-overview.md#signed-requests); the enrollment request carries the token instead. EvoHub also sees the address the requests come from. ## Each object For every object of the kinds it may list (see [What it is allowed to do](https://docs.evohub.io/agent-kubernetes.md#what-it-is-allowed-to-do)), the agent sends three things: - **An identity**, as a path: `k8s://////`. The cluster ID is the uid of your `kube-system` namespace. - **A content hash**: the SHA-256 of the redacted content. When EvoHub already holds that content, only the hash is sent. - **The redacted content**: the object as the Kubernetes API returns it, redacted as below, with `resourceVersion`, `generation`, `managedFields` and `selfLink` removed from `metadata`, `spec` kept in full (redacted), and `status` removed except for these fields: | Kind | `status` kept | | --- | --- | | Node | `nodeInfo` (machine ID, system UUID, boot ID, kernel version, OS image, architecture, kubelet and container runtime versions), `addresses`, `capacity` | | Service, Ingress | `loadBalancer`: assigned IPs and hostnames | | Namespace | `phase` | | PersistentVolumeClaim | `phase`, `capacity` | In practice EvoHub receives, for example: container names, images, ports, resource requests and limits; the non-secret parts of commands and arguments; probe paths and ports; the **names** of environment variables, volumes, Secrets, ConfigMaps, ServiceAccounts and image pull secrets; volume sources such as an NFS server or a host path; labels, and annotations that pass the rules below; Service cluster IPs and load balancer ranges; Ingress hosts, paths and TLS Secret names; a Node's provider ID, pod CIDR and taints; HPA targets; and PVC storage classes and sizes. An object larger than 256 KiB after redaction is sent with only its name, namespace, uid, labels, owner references and creation time. ## Live state Every minute the agent reports how each Deployment, StatefulSet, DaemonSet and CronJob is running now: replicas wanted, ready, available and updated; its pods counted by phase; their restarts; and how many containers are in each failing state, by the state's name only (`CrashLoopBackOff`, `ImagePullBackOff`, `OOMKilled` and a few others; anything else is sent as `Other`). For the service map it adds one line per pod, at most 200 per workload: the pod's name, ReplicaSet, node, phase, readiness, restarts, start time and failing state's name. No pod spec, no environment, no messages, nothing a container printed. EvoHub keeps only the latest report, to show health, and treats one older than ten minutes as unknown. Set `catalog.liveState.enabled=false` in the chart to send none, and the agent then has no access to pods at all. ## Redaction The agent applies these rules inside your cluster, before an object is hashed or sent. Rules 1 to 6 are structural: they apply wherever the field is, whatever it holds. 1. **Environment variable values.** Every value is removed, whatever the variable is called, in containers, init containers and ephemeral containers. The variable's name stays, and so does what a `valueFrom` points at, such as the name and key of a `secretKeyRef`. 2. **Command lines**: a container's `command` and `args`, and any probe's or lifecycle hook's `exec`. - `--password=x`, `PASSWORD=x` and `-px` lose their value. - The argument after a bare `--password` (or `--token`, `--secret`, …) is replaced. - The whole program after an interpreter's script flag is replaced: `-c`, `-ec`, `-lc` and similar, `-e`, `--eval` and `-Command`. 3. **Probe headers.** Every probe `httpHeaders` value is replaced. 4. **Volume drivers.** Every volume driver `options` and `volumeAttributes` value is replaced. 5. **Annotations**, in every annotations map including pod templates: - `kubectl.kubernetes.io/last-applied-configuration` is removed. - Annotations under `evohub.io/`, `app.kubernetes.io/`, `meta.helm.sh/release-`, `prometheus.io/` and `deployment.kubernetes.io/revision` are kept as they are. - Any other annotation keeps its value only if the value is at most 256 bytes, one line, not JSON, and the key does not end in `snippet`. 6. **Secrets and ConfigMaps.** A Secret's `data` and `stringData` would be removed and a ConfigMap reduced to its key names. The agent does not list either; this rule is there for safety only. 7. **A final check on every remaining string.** A string is replaced if: - its key ends in a word such as `password`, `secret`, `token`, `apikey`, `credentials`, `auth`, `dsn` or `_key` - it looks like a credential: an AWS key ID, a Stripe, GitHub, GitLab, Slack or Google key, a JWT, a private key, an `Authorization:` header, a bearer or basic credential, a URL with a password or a token in its query, or an EvoHub token - it looks like random key material: 24 or more characters with no spaces, mixing upper case, lower case and digits, with high entropy Replaced values read `[REDACTED]`. The hash is computed after redaction, so a rotated secret is not a change. ### What the rules guarantee, and what they do not Rules 1 to 6 are structural, so whatever a field they cover holds is removed. Rule 7 is pattern-based and catches common forms only. It can miss, for example: - a short password passed as a positional argument (`redis-cli -a x`) - a token that is all lower case or hex - a secret written into a label or an unlisted spec field Where a value is in an environment variable, a mounted Secret or a `secretKeyRef`, it is never sent. EvoHub runs a scanner of the same kind again when data arrives, and never logs request bodies. ## What it never sends - the value of any environment variable, secret or not - the contents of any Secret or ConfigMap - kubectl's last-applied configuration, and the values of annotations that fail the rules above - the scripts given to `sh -c` and similar - pod specs, logs, events, metrics, or anything from inside a container - its own private key - the enrollment token, once it has enrolled The agent's own log records counts, IDs (agent, cluster, sweep), its public key, and error messages from EvoHub and Kubernetes. It never records any object's content. ## Related - [EvoHub Agent overview](https://docs.evohub.io/agent-overview.md) - [Install on Kubernetes](https://docs.evohub.io/agent-kubernetes.md) - [Install on a VM](https://docs.evohub.io/agent-vm.md) - [Security at EvoHub](https://docs.evohub.io/security.md) --- Source: https://docs.evohub.io/evotrail-overview.md # EvoTrail overview EvoTrail gathers what happens across your EvoHub organization in one place. Its **Metrics** tabs show each product's numbers for a period you pick — alerts and response times, uptime, status page and docs readers, finished cards, retros held, spend — and its **Audit Logs** list who did what, in every product. This page covers Metrics; see [Audit Logs](https://docs.evohub.io/evotrail-audit-logs.md) for the trail. Reading EvoTrail is free: it is never metered. ## Open EvoTrail - Open the **Services** menu in the top bar and choose **EvoTrail**. - On the home page, use the **EvoTrail** card under **Your Services**, or add the **EvoTrail** shortcut to **Quick actions** (see [Your home page](https://docs.evohub.io/your-home-page.md)). - In a product, a link to EvoTrail (such as **View in EvoTrail**) opens that product's tab. From a docs site, changelog, status page or monitor, the tab opens already narrowed to it. The products' own analytics screens have moved here. An old link to a docs site's, changelog's or status page's **Analytics** screen opens its EvoTrail tab instead. ## The tabs The left-hand navigation lists **Overview**, **Usage**, and a tab for every product you may read, then **Audit Logs** under **Trail**. | Tab | What it shows | | --- | --- | | **Overview** | What needs a look, each product's headline numbers, the incidents of the period and what they did to your status pages, and a timeline. | | **Usage** | How much each product is used: actions, active people and teams, top actions and products nobody used. | | **On-Call** | Alerts by severity, time to acknowledge and resolve, noisiest alerts and integrations, flapping alerts, notifications by channel, responder load (including off-hours and sleep-hours pages), incidents and postmortems. | | **Uptime** | Uptime over time, every monitor with its SLA target, worst monitors, response time (typical and slowest 5%), downtime and time to recover, uptime by team. | | **Status Pages** | Views and visitors per day, feed reads, AI readers, page types, product pages, subscriber growth and the incidents each page told its readers about. | | **Docs** | Per site: **Overview**, **Search**, **AI readers**, **Feedback** and **Content gaps** (searches that found nothing and missing pages readers hit), filtered by version and language. | | **Changelog** | Per changelog: views and visitors, What's new widget opens and clicks, reactions, feedback, feed reads, AI readers, entries, emails sent and subscriber growth. | | **Board** | Cards finished per week, cycle time, cumulative flow, work in progress per list, overdue cards, review results and work by person. | | **Retro** | Retros held by team, participation, cards, votes and comments, and action items created and completed. | | **Billing** | Usage in the range, charged so far this month, free allowance left, the month-end estimate, EHU per day by product and spend per month. | A product tab is offered only to people who may read that product, and each tab reads with your own permissions. **Billing** is shown only to people who hold the billing usage permission. A part of a tab that is not available yet says **Coming soon** instead of showing a guessed number. ## Pick the period The bar at the top of every Metrics tab sets what all of its cards show: - **Date range**: **Last 7 days**, **Last 30 days**, **Last 90 days**, **Last 12 months** or **Custom range**. A custom range can be up to 366 days. Ranges are whole days in UTC. - **Compare with**: **No comparison**, **Previous period** (the same length just before) or **Same period last year**. Numbers then show their change against it, and some charts draw it as a dashed line. - **Team** (on Overview, Usage, On-Call, Uptime, Board and Retro): **All teams**, **Organization-wide only**, or one team. - **Share view** copies a link to exactly this view. Teammates who open it see it with their own permissions. Under the bar, each product tab says how far back its data goes, for example "Daily figures are kept for 400 days; individual checks for 90". ## Overview - **The attention line** appears only when something in the period needs a look: incidents opened, monitors that missed their SLA, monitors down right now. - **Product cards** show each product's headline numbers with their change and a small trend, for example alerts and the median time to acknowledge, or cards finished and the median time to finish one. **Open →** goes to the product's tab. A product you have not started using says **Not in use yet** with a **Set it up** link. - **Incident impact** lists every incident in the range: severity, when it started, time to acknowledge, time to resolve, how long until customers were told on a status page, status page views during the incident against the same hours a week earlier, new subscribers, monitor downtime on the same days, and the postmortem's state. - **Timeline** puts incidents and monitor downtime above status page, docs and changelog views on one time axis, with changelog releases marked, so you can see what customers read while something was wrong. Each row has its own scale. ## Usage **Usage** is built from the audit trail. It counts everything people, API keys and automation did in the products you can read: - **Usage at a glance**: **Actions**, **Active people**, **Products in use** and **Quiet products**. - **Actions per product** over time. Click a bar to open the Audit Logs at that time. - **By product**: actions, change against the previous period, people and when the product was last used. Open a product to see its rows in the Audit Logs. - **Top actions** and **Most active teams**. - **Most active people**, visible to organization owners and administrators only, whatever a role holds. - **Quiet products**: products you can read that nobody used in the period. ## AI readers Status pages, docs sites and changelogs count how AI assistants and crawlers read them. Two numbers are kept: - **AI reads** count each page once per assistant per day. A crawler fetching the same page, or `llms-full.txt`, fifty times in a day is one read; two pages, or two assistants on one page, are two. - **AI requests** count every fetch. The **AI readers** number shows reads. Its explanation gives the raw requests too, and says how many days of the period come from before reads were counted; those days count every request. ## Download a PDF report Select **Download PDF** above any Metrics tab and choose **All products** or **Only** one product. The report uses the range, comparison, team and filters you have on screen, and includes only the products you may read. If none of them is yours to read, no PDF is made. ## CSV Most cards and tables have a **CSV** button that downloads exactly the rows shown. ## How long data is kept | Data | Kept for | | --- | --- | | Audit trail: changes | 365 days | | Audit trail: activity, such as moving a card | 90 days | | IP addresses and browser details on audit events | 90 days | | On-Call alerts and their notifications | 1 year | | Uptime daily figures | 400 days | | Uptime individual checks (needed for latency detail) | 90 days | | Status page, docs and changelog visit figures | Each site's own setting, 365 days unless changed | | Docs page feedback | 2 years | | Board cards and their activity | While the board exists; archived cards still count | The audit trail periods are the same for every organization and cannot be changed. ## Permissions | To | You need | | --- | --- | | Open EvoTrail's Metrics | `evotrail:metrics:read` (every default role), and for each product tab that product's read permission | | Open the Billing tab | `billing:usage:read` | | See **Most active people** | Organization owner or administrator | | Open Audit Logs | `evotrail:audit:read` (Owner, Admin and Member by default) — see [Audit Logs](https://docs.evohub.io/evotrail-audit-logs.md) | To read the same data with an API key, see the [EvoTrail API](https://docs.evohub.io/evotrail-api.md). ## Related - [Audit Logs](https://docs.evohub.io/evotrail-audit-logs.md) - [EvoTrail API](https://docs.evohub.io/evotrail-api.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) - [How billing works](https://docs.evohub.io/how-billing-works.md) --- Source: https://docs.evohub.io/evotrail-audit-logs.md # Audit Logs **Audit Logs** is your organization's trail: who did what, when, in every EvoHub product, in one list. It lives in EvoTrail, under **Trail** in its left-hand navigation. This page explains how to read it, filter it and export it, and who can see which rows. ## Open Audit Logs - Open **EvoTrail** from the **Services** menu and choose **Audit Logs**. - Or add the **Audit Logs** shortcut to the home page's **Quick actions**. The old **Organization → Audit Logs** address opens this page. ## What a row shows Each row has the action in words, for example **Alert acknowledged**, **Role assigned to team** or **Two-factor sign-in turned on**, the product it happened in, who did it and when. The newest rows come first, 50 to a page; use **Older** and **Newer** to page. Who did it is the person's name, or: - **API key** and the key's id, for an action taken with an [API key](https://docs.evohub.io/api-keys-and-scopes.md), - **Automation**, for something EvoHub did on its own, such as an integration resolving an alert, - **EvoHub support**, for an action taken by EvoHub's support team. Click a row to see what changed: **Before**, **After** and **Details**. ## Filter - **Product**: **Everything**, or click one or more products. Each shows how many rows it has. - **From** and **To**: the days to look at. - **Who**: **Anyone**, or one person, with their number of rows. - **Action**: type an action code, such as `alert.acknowledged`, or end it with `*` to match a group, such as `alert.*`. The suggestions list the actions found, in words and as codes. - **Team**: **Any team**, or the rows about one team's things. - **Kind**: **Everything**, **Changes (kept 365 days)** or **Activity (kept 90 days)**. Changes are things such as editing a schedule or revoking a key; activity is day-to-day work such as moving a card. - **More filters**: **Request id**, **IP address**, **Resource type** and **Resource id**. **Clear filters** puts everything back. The filters are part of the page's address, so a filtered view can be bookmarked or shared; the people you share it with see only the rows they may read. Numbers in EvoTrail's **Usage** tab link here with the matching filters already set. ## Export **Export CSV** downloads every row that matches the current filters, not just the page on screen, up to 100,000 rows. If more rows match, narrow the date range or filters and export again. ## Who sees what Opening Audit Logs needs `evotrail:audit:read`. Owner, Admin and Member hold it by default; Viewer does not. On top of that, each row needs its product's audit permission. A row you may not read is simply not listed. | Product | Rows need | | --- | --- | | Organization (members, teams, roles, keys, sign-ins) | `identity:audit:read` | | On-Call | `oncall:audit:read` | | Uptime | `uptime:audit:read` | | Status Pages | `status:audit:read` | | Retro | `retro:audit:read` | | Support | `support:audit:read` | | Board | `board:audit:read` | | Docs | `docs:audit:read` | | Changelog | `changelog:audit:read` | By default, Members hold the audit permissions of Organization, On-Call, Uptime, Status Pages, Retro and Support. The Board, Docs and Changelog trails are an administrator's; give them to others with a [custom role](https://docs.evohub.io/roles-and-permissions.md#custom-roles). A person whose own role is Owner or Admin sees every row. ## How long rows are kept - **Changes**: 365 days. - **Activity**: 90 days. - **IP addresses and browser details** on a row: 90 days, after which the row stays without them. These periods are the same for every organization and cannot be changed. To keep rows longer, export them regularly, or pull them with the [EvoTrail API](https://docs.evohub.io/evotrail-api.md). ## Related - [EvoTrail overview](https://docs.evohub.io/evotrail-overview.md) - [EvoTrail API](https://docs.evohub.io/evotrail-api.md) - [Security](https://docs.evohub.io/security.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) --- Source: https://docs.evohub.io/how-billing-works.md # How billing works EvoHub charges for what your organization actually uses, never for how many people are in it. This page explains the unit everything is measured in, the free allowance, the rates, and what each action costs. The [pricing page](https://evohub.io/pricing) has the same rates and a cost calculator. ## EvoHub tokens (EHU) Everything billable in EvoHub is measured in **EvoHub tokens (EHU)**. Each action has a fixed weight in EHU: a voice call is 10 EHU, an alert received is 1 EHU, and so on. At the end of the month, EvoHub adds up your organization's EHU, takes off the free allowance, and charges the rest at your rate. Using EHU rather than a dollar price per action keeps every product on one bill and one allowance. ## Free allowance Every organization gets **200 EHU free every month**, from the day it signs up. The allowance: - resets at the start of each calendar month, - does not carry over to the next month, - applies to paying organizations too. You are only ever charged for usage above it. A month spent entirely inside the allowance costs nothing, and you do not need a card on file to use EvoHub within it. ## Rates How much one EHU costs depends on how you pay. | How you pay | Price per EHU | | | --- | --- | --- | | **On-demand** | $0.015 | Pay as you go. Charged to your card after each month. | | **Commitment** | $0.012 | Commit to a monthly volume for a 12-month term. | | **Prepaid credit** | $0.009 | Buy EHU bundles up front. Bundles never expire. | There are no per-user fees and no monthly base fee. See [Usage and payments](https://docs.evohub.io/usage-and-payments.md) for how commitments and credit work. ## What costs what | Action | Cost | Notes | | --- | --- | --- | | On-Call voice call | 10 EHU (about $0.15) | Each call placed to a person | | On-Call alert received | 1 EHU (about $0.015) | Each alert taken in from an integration | | Email notification | 0.7 EHU (about $0.0105) | On-Call email notifications | | Mobile push notification | Free | | | Outbound webhook | Free | | | Uptime check | 0.001 EHU per check | A 1-minute monitor checked from one location is about $0.65 a month | | Uptime heartbeat monitor | 0.001 EHU per status change | One check when it is first evaluated, then one each time it goes down or comes back; pings are not counted | | Status page on a custom domain | 0.9 EHU per page-hour | About $9–10 a month per page | | Status page subscriber email | 0.7 EHU per email | Each update email delivered to a subscriber | | Docs site on a custom domain | 2.5 EHU per site-hour | About $27 a month per site, any number of editors | | Changelog on a custom domain | 0.9 EHU per site-hour | About $9–10 a month per changelog | | Changelog subscriber email | 0.7 EHU per email | Each update or digest email delivered to a subscriber | | Catalog environment | 11 EHU per environment-day | About $5 a month for each environment an agent or a cloud account reports from, however many clusters or machines are in it. Services, agents, cloud accounts, discovered resources, the map, drift and SLA reports are free | Prices in dollars are at the on-demand rate. - **Hourly items** (status pages, docs sites and changelogs on a custom domain) are charged for every hour they are published on your own domain, whatever the traffic. An average month has 730 hours. - **Only what happened is billed.** A notification that was not sent, for example because it was suppressed or failed, is not charged. - **SMS** is not available yet; it is coming soon, and it has no price until it ships. EvoHub notifies by voice call, mobile push and email. - **Members, teams, retros and boards** cost nothing. > [!NOTE] > Weights are fractional, so EHU are added up exactly during the month and rounded up to a whole EHU once, on the month's total, never per event or per line. ## Examples These examples use a 30-day month and the on-demand rate. :::details{title="A small team that stays free"} | Usage | EHU | | --- | --- | | 50 alerts received | 50 | | 5 voice calls | 50 | | 2 uptime monitors every minute, one location | 86.4 | | Push notifications | 0 | | **Total** | **186.4** | 186.4 EHU is inside the 200 EHU allowance, so the month costs **$0**. ::: :::details{title="An on-call team with a public status page"} | Usage | EHU | | --- | --- | | 300 alerts received | 300 | | 20 voice calls | 200 | | 100 notification emails | 70 | | 5 uptime monitors every minute, one location | 216 | | 1 status page on a custom domain (720 hours) | 648 | | **Total** | **1,434** | 1,434 − 200 free = 1,234 EHU × $0.015 = **about $18.51** for the month, whether the team has 5 people or 50. ::: ## Paying for what you use Usage is paid for in a fixed order: 1. **Free allowance** first. 2. **Credit** next, if you have bought any. 3. **Commitment**, if you have one in force. 4. **On-demand**, charged to your card. You are billed once a month, after the month closes, never per call or per message. See [Usage and payments](https://docs.evohub.io/usage-and-payments.md). ## Related - [Usage and payments](https://docs.evohub.io/usage-and-payments.md) - [What is EvoHub](https://docs.evohub.io/what-is-evohub.md) - [Pricing page](https://evohub.io/pricing) --- Source: https://docs.evohub.io/usage-and-payments.md # Usage and payments Everything about what your organization uses and pays lives under **Usage & Billing** in the avatar menu. This page walks through each screen: the overview and estimate, usage by service, statements, payment method, credit and commitment. ## Who can see and change billing - Every member can read usage, statements and the saved cards. The default Viewer, Member and Admin roles all include the billing read permissions. - Adding or removing a card, changing billing details, buying credit and committing to a volume are for Owners and Admins, or anyone given the matching permission (**Payment Methods → Write**, **Billing Profile → Write**, **Credit → Write**, **Commitment → Write**) through a custom role. The ready-made **Billing Admin** role holds all of them. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). ## Overview **Overview** answers "what will this month cost?": - **Estimated bill this month**: what you are likely to pay for the month, after your free allowance and credit, estimated from the last 7 days. - **Charged so far**: what has actually been charged, the EHU used this period, and your rate. - **Billing cycle**: the day of the month you are on and when usage resets. - **Daily usage**: a chart of EHU per day. - **Free allowance**: how much of this month's free EHU is left. Marked **Running low** and **Used up** as it runs out. - **Credit**: your prepaid balance, if any. - **Commitment**: your committed monthly volume and how much of it you have used, if you have a commitment. > [!WARNING] > Without a card or credit, alert notifications stop once the month's free allowance is used up. Alerts keep being received and recorded, so nothing is lost, but nobody is paged. The Overview says so in red when it happens, and adding a card or buying credit resumes notifications. Add a card before you need it. ## Usage **Usage** shows everything your organization used this period, broken down by service and by metric, with: - **How your charge is worked out (so far)**: usage, minus the free allowance, minus credit, priced at your rate. - **List total (before free allowance)**: what the usage would cost at list price. This is not your bill. - **Daily usage**: the same daily chart as the Overview. ## Statements You get one statement per month. Each month goes through these stages: | Status | Meaning | | --- | --- | | **current period** | The month in progress. Figures update as usage comes in. | | **estimate — not charged** | The month has ended and the statement is being completed. Nothing has been charged yet. | | **finalized — not yet charged** | The statement is frozen and will be charged shortly. | The statement is finalized a few days after the month ends, so that late usage is included, and only a finalized statement is ever charged. Once charged, the status shows what happened to the money instead: | Status | Meaning | | --- | --- | | **No charge** | The month stayed inside the free allowance or was covered by credit. | | **Carried over** | The amount was too small to charge to a card (under $0.50). It is added to the next month's charge, not written off. | | **No card on file** | There was something to pay but no card to charge. | | **Awaiting payment** | An invoice was raised and payment is pending. | | **Paid** | The invoice was paid. | | **Payment failed** | The card was declined. | Select a month to see **How this was worked out**: metered usage, the free allowance, what was paid from credit, the committed volume and anything above it. The **Documents** column links to the invoice and receipt once they exist. The **Payments** list below the statements shows every charge, whether a monthly usage bill, a credit bundle or the cost of ending a commitment early, with its invoice and receipt. ## Payment method **Payment Method** has two parts. ### Billing details What appears on your invoices: **Billing as** (**A business** or **An individual**), the legal name, the **Invoice email** (where invoices, receipts and billing notices are sent), the address, and an optional **Business tax number**. Select **Save billing details**. Fill these in before your first invoice; tax is worked out from this address. ### Saved cards :::steps ### Add a card Select **Add a card** (or **Add another card**). The card form is provided by Stripe, EvoHub's payment processor, and card numbers never reach EvoHub's servers. ### Save it Enter the card details and select **Save card**. It can take a moment for the card to appear while Stripe confirms it. ### Choose the default The default card is the one monthly bills are charged to. Select **Make default** on another card to change it, or **Remove** to delete one. ::: Card is the only payment method offered. ## Credit **Credit** lets you buy EHU up front at the prepaid rate, the lowest rate EvoHub offers ($0.009 per EHU). - Choose a bundle size (1,000, 5,000, 25,000 or 100,000 EHU) or enter your own amount. The minimum is 1,000 EHU. - You need a saved card first. Pay with a saved card or a different one, then confirm the payment. - Credit is added once the payment goes through, and an invoice and receipt appear under **Payments**. - Purchased bundles never expire, and they are non-refundable. - Your balance is spent each month after the free allowance and before your card is charged. The **Where your credit came from** section separates **Purchased bundles** from credit **Granted by EvoHub**. Granted credit can carry an expiry date, shown next to it. **History** lists every purchase, grant and monthly use of credit. ## Commitment **Commitment** gives you a lower rate ($0.012 per EHU, 20% below on-demand) in exchange for committing to a monthly volume for a 12-month term. - Enter the **EHU per month** you commit to, read and accept the commitment agreement, and confirm. The term starts at the beginning of the next month. - You are billed for the full committed volume every month, whether or not you use it. Unused volume does not carry over. - Usage above the commitment is billed at the on-demand rate. - You can increase your commitment at any time; this starts a fresh term at the higher volume. You cannot lower it during the term. - A commitment does not renew on its own. EvoHub emails you 30 days before the term ends, and afterwards you return to on-demand pricing. - **End this commitment early** recharges the months already served at the on-demand rate and invoices the difference. The exact amount is shown before you confirm. ## Related - [How billing works](https://docs.evohub.io/how-billing-works.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) - [Privacy and your data](https://docs.evohub.io/privacy-and-data.md) --- Source: https://docs.evohub.io/api-overview.md # API overview The EvoHub API lets scripts, CI jobs and other systems read and change what is in your organization: alerts, schedules, monitors, status pages and more. This page covers what every request has in common. ## API references by product Each product has a guide to its common tasks and a full reference with every endpoint, its parameters, schemas, errors and the scope it needs. | Product | Guide | Reference | | --- | --- | --- | | On-Call | [On-Call API](https://docs.evohub.io/on-call-api.md) | [On-Call API reference](https://docs.evohub.io/oncall.md) | | Uptime | [Uptime API](https://docs.evohub.io/uptime-api.md) | [Uptime API reference](https://docs.evohub.io/uptime.md) | | Status Pages | [Status API](https://docs.evohub.io/status-api.md) | [Status API reference](https://docs.evohub.io/status.md) | | EvoTrail (audit trail, usage, metrics) | [EvoTrail API](https://docs.evohub.io/evotrail-api.md) | [EvoTrail API reference](https://docs.evohub.io/evotrail.md) | | Organization (members, teams, roles, invitations) | [Organization API](https://docs.evohub.io/organization-api.md) | [Organization API reference](https://docs.evohub.io/organization.md) | | Catalog (services, inventory, drift, SLA reports) | [Catalog API](https://docs.evohub.io/catalog-api.md) | [Catalog API reference](https://docs.evohub.io/catalog.md) | | Retro | [Retro boards](https://docs.evohub.io/retro-boards.md) | [Retro API reference](https://docs.evohub.io/retro.md) | | Board | [Boards](https://docs.evohub.io/boards.md) | [Board API reference](https://docs.evohub.io/board.md) | | Billing (usage, statements, payments) | [How billing works](https://docs.evohub.io/how-billing-works.md) | [Billing API reference](https://docs.evohub.io/billing.md) | | Support (tickets) | — | [Support API reference](https://docs.evohub.io/support.md) | | Docs and Changelog | [Docs and Changelog API](https://docs.evohub.io/docs-and-changelog-api.md) | Not published yet | | Alert ingest (sending alerts from monitoring tools) | [Generic webhook](https://docs.evohub.io/generic-webhook.md) | [Alert ingest API reference](https://docs.evohub.io/alert-ingest.md) | ## Base URL All endpoints live under one base URL: ``` https://evohub.io/api/v1 ``` Every request must use HTTPS. Paths in these docs are relative to the base URL, so `GET /monitors` means `GET https://evohub.io/api/v1/monitors`. > [!NOTE] > Sending alerts *into* EvoHub from a monitoring tool does not use the API key flow described here. Each On-Call integration has its own URL with its own credential. See [Generic webhook](https://docs.evohub.io/generic-webhook.md). ## Authentication Authenticate with an **API key**. Create one in the console under **My Account → API keys**, or as an organization key under **Organization → Organization Keys**. See [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md). Send the key in either of these headers. They are equivalent; use one. :::code-group ```bash [Authorization header] curl https://evohub.io/api/v1/monitors \ -H "Authorization: Bearer evohub_YOUR_KEY" ``` ```bash [X-API-Key header] curl https://evohub.io/api/v1/monitors \ -H "X-API-Key: evohub_YOUR_KEY" ``` ::: - Every EvoHub API key starts with `evohub_`. - A key works in exactly one organization, so you never pass an organization ID. - If you send both headers, they must carry the same key, or the request is refused. - A missing, unknown, revoked or expired key gets **401 Unauthorized**. A valid key that lacks the scope for an endpoint gets **403 Forbidden**. See [Errors](https://docs.evohub.io/errors.md). ## A first request List your organization's uptime monitors. The key needs the `uptime:monitor:read` scope. ```bash curl https://evohub.io/api/v1/monitors \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` ```json { "data": [ { "id": "…", "name": "Marketing site", "url": "https://www.acme.example", "type": "http", "interval_seconds": 60, "is_active": true, "last_status": "up", "last_checked_at": "2026-10-09T08:15:00Z", "created_at": "2026-09-01T10:00:00Z", "updated_at": "2026-10-01T12:30:00Z" } ] } ``` The example shows a subset of the fields a monitor returns. ## Requests and responses - Send request bodies as JSON with `Content-Type: application/json`. - Successful responses are JSON with the result in a `data` field. Many endpoints also include `"success": true`. - Errors are JSON with an `error` object instead. See [Errors](https://docs.evohub.io/errors.md). - Timestamps are RFC 3339 strings in UTC, for example `2026-10-09T08:15:00Z`. Send timestamps in the same format. - IDs are opaque strings. Store them as they are; do not parse them. ## Pagination Most list endpoints return the whole list. Where a list can grow large, it is paged with `limit` and `offset` query parameters, and the response carries the total number of matches in `meta.total` next to `data`. For example, the On-Call alert list returns 50 alerts by default. The key needs the `oncall:alert:read` scope. ```bash curl "https://evohub.io/api/v1/alerts?status=triggered&limit=20&offset=0" \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` ```json { "data": [ { "id": "…", "title": "High error rate on checkout", "severity": "critical", "status": "triggered", "created_at": "2026-10-09T08:01:12Z" } ], "success": true, "meta": { "total": 3 } } ``` To read the next page, add `limit` to `offset` and request again until you have `meta.total` items. ## Request IDs Every response carries an `X-Request-ID` header, and most error bodies repeat it as `request_id`. Include it when you contact support about a request; it lets us find exactly that call. You can also send your own `X-Request-ID` header (up to 64 letters, digits, `-` or `_`), and EvoHub uses it instead of generating one. Any other value is replaced with a generated ID. ## Rate limits Requests are rate limited per API key (or, for requests without a key, per IP address), counted over a 10-second window. The limit is generous, on the order of 100 requests per second, and is meant to stop runaway scripts, not normal automation. When you go over it, EvoHub answers **429 Too Many Requests** with a `RATE_LIMITED` error and a `Retry-After: 10` header. Wait at least that many seconds before retrying, and back off further if it happens again. Responses also carry `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers. Alert ingest URLs and heartbeat ping URLs are not subject to this limit, so a burst of alerts during an outage is never dropped for being too many. ## Related - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) - [Errors](https://docs.evohub.io/errors.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) --- Source: https://docs.evohub.io/api-keys-and-scopes.md # API keys and scopes An API key lets a script, CI job or another system call the EvoHub API without a person signing in. Each key carries **scopes**: the exact permissions it has. This page explains the two kinds of key, how to create and revoke them, and their limits. ## Two kinds of key | | Personal key | Organization key | | --- | --- | --- | | Where | **My Account → API keys** | **Organization → Organization Keys** | | Who can create one | Anyone signed in | People with the **Organization Keys → Write** permission (Owners and Admins by default) | | What it can do | The scopes you pick, never more than you hold yourself | Exactly what its role grants, as the role changes over time | | What it sees | What you see: organization-wide things and your teams' things, as your teams are when the key is used | Organization-wide things, plus one team's when it is scoped to that team | | How long it lives | Stops working when you leave the organization or delete your account | Belongs to the organization and outlives the people who manage it | | Best for | Your own scripts and experiments | CI pipelines and integrations that must keep running | Both kinds work in one organization only, and both use the same headers. See [API overview](https://docs.evohub.io/api-overview.md#authentication). ## Create a personal key :::steps ### Open API Keys Open the avatar menu, choose **My Account**, then **API keys**, and select **Create Key**. ### Name it Enter a **Name** that says what the key is for, for example "Deploy pipeline". The name is the only way to tell keys apart later. ### Pick the organization Under **Organization**, choose which of your organizations the key works in. ### Choose an expiry Under **Expires**, pick **90 days** (the default), **180 days**, **1 year** or **Never expires**. ### Choose scopes Tick the permissions the key needs. They are grouped by product, as in the role editor. Anything you do not hold yourself in that organization is greyed out. Selecting a write scope also selects the read it depends on. ### Copy the key Select **Create Key**. The key is shown once, in the **Copy your key now** dialog. Copy it and store it in your secret manager, then select **I have copied it**. ::: > [!WARNING] > EvoHub stores only a hash of each key, so a key cannot be shown again or recovered. If you lose it, revoke it and create a new one. A key with no scopes can authenticate but is refused everywhere. That is the safe default, not an unrestricted key. A personal key is always a slice of you, measured at the moment it is used. If your own permissions shrink, for example because an administrator changed your role, the key loses those permissions too. The same goes for teams: the key sees your teams' schedules, monitors, boards and other team-scoped things, and when you join or leave a team, the key follows. If you leave the organization or delete your account, the key stops working. ## Create an organization key :::steps ### Open Organization Keys Go to **Organization → Organization Keys** and select **Create Key**. ### Name it Enter a **Name**, for example "GitHub Actions — deploy". ### Pick a role Under **Role**, choose the role whose permissions the key should have. You can only pick a role whose permissions you hold yourself, and only an administrator can give a key the Admin role. Create a [custom role](https://docs.evohub.io/roles-and-permissions.md#custom-roles) that holds exactly what the integration needs. ### Choose what it sees Under **Sees**, keep **Whole organization** or pick a team. A key scoped to a team sees that team's boards, schedules and monitors, the same view a member of that team has. ### Choose an expiry and copy the key Pick an expiry under **Expires**, select **Create Key**, and copy the key from the dialog. As with personal keys, it is shown only once. ::: An organization key's scopes are its role's, read live. Edit the role and every key on it changes; delete the role and every key on it stops working. ## Scopes Scopes are the same permissions roles are built from. They are grouped by product: | Product | Scopes | | --- | --- | | Organization | `identity:user:read`, `identity:user:write`, `identity:user:invite`, `identity:team:read`, `identity:team:write`, `identity:role:read`, `identity:role:write`, `identity:org:write`, `identity:apikey:read`, `identity:apikey:write`, `identity:agent:read`, `identity:agent:write`, `identity:cloudaccount:read`, `identity:cloudaccount:write`, `identity:audit:read` | | On-Call | `oncall:alert:read`, `oncall:alert:write`, `oncall:alert:respond`, `oncall:incident:read`, `oncall:incident:write`, `oncall:schedule:read`, `oncall:schedule:write`, `oncall:escalation:read`, `oncall:escalation:write`, `oncall:integration:read`, `oncall:integration:write`, `oncall:maintenance:read`, `oncall:maintenance:write`, `oncall:postmortem:read`, `oncall:postmortem:write`, `oncall:settings:write`, `oncall:audit:read` | | Uptime | `uptime:monitor:read`, `uptime:monitor:write`, `uptime:incident:read`, `uptime:silence:read`, `uptime:silence:write`, `uptime:channel:read`, `uptime:channel:write`, `uptime:audit:read` | | Status Pages | `status:page:read`, `status:page:write`, `status:component:read`, `status:component:write`, `status:incident:read`, `status:incident:write`, `status:maintenance:read`, `status:maintenance:write`, `status:subscriber:read`, `status:subscriber:write`, `status:audit:read` | | Retro | `retro:board:read`, `retro:board:write`, `retro:board:delete`, `retro:card:read`, `retro:card:write`, `retro:action:read`, `retro:action:write`, `retro:audit:read` | | Board | `board:board:read`, `board:board:write`, `board:board:delete`, `board:list:read`, `board:list:write`, `board:card:read`, `board:card:write`, `board:member:write`, `board:review:write`, `board:audit:read` | | Docs | `docs:site:read`, `docs:site:write`, `docs:site:delete`, `docs:page:write`, `docs:page:publish`, `docs:audit:read` | | Changelog | `changelog:site:read`, `changelog:site:write`, `changelog:site:delete`, `changelog:entry:write`, `changelog:entry:publish`, `changelog:audit:read` | | Billing | `billing:usage:read`, `billing:invoice:read`, `billing:payment:read`, `billing:payment:write`, `billing:profile:read`, `billing:profile:write`, `billing:credit:write`, `billing:commitment:write` | | Support | `support:audit:read` | | EvoTrail | `evotrail:metrics:read`, `evotrail:audit:read`, `evotrail:settings:write` | | Catalog | `catalog:component:read`, `catalog:component:write`, `catalog:resource:read`, `catalog:resource:write`, `catalog:source:read`, `catalog:source:write` | What each permission covers is described in [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md#permissions-reference); Catalog's, and the agent and cloud account permissions it uses, in [Catalog permissions](https://docs.evohub.io/catalog-permissions.md). No scope lets a key send inventory into Catalog: only an enrolled agent can. Give a key the fewest scopes it needs. A key that only reads monitors needs `uptime:monitor:read` and nothing else. ## What a key can never do API keys are for automation, and some actions are reserved for a person signed in to the console. - **A key never counts as an administrator.** Owners and Admins pass every check when they act in person, but a key is held to its scopes, even if it was created by an Admin or carries the Admin role. A key never sees another team's work just because its creator could. - **A key cannot approve or review.** Approving or requesting changes on docs change requests and changelog drafts, and merging or reverting a docs change request, must be done by a person. So must connecting a docs site or a changelog to GitHub and issuing access credentials for a private docs site. A key that tries gets 403 `PERSON_REQUIRED`. - **A key cannot act on a person's own account.** It cannot switch organizations, create an organization, change a profile or password, manage sessions, two-factor authentication or connected accounts, accept invitations, or delete an account. These return 403 `API_KEY_NOT_ALLOWED`. - **A key cannot manage API keys.** Creating, listing and revoking keys, of either kind, is done in the console. - **An organization key cannot send invitations**, because an invitation names the person who sent it. Use a personal key with `identity:user:invite`. ## Revoke a key Revoking stops a key immediately, and anything still using it starts failing at once. It cannot be undone. - **Personal key:** in **My Account → API keys**, select **Revoke** on the key's row and confirm. You can revoke your own keys in any organization. - **Organization key:** in **Organization → Organization Keys**, select **Revoke** on the key's row and confirm. This needs the **Organization Keys → Write** permission. Your **API keys** list shows each key's prefix, organization, scopes (hover the count to see them), state (**active**, **revoked** or **expired**), when it was last used and when it expires. A key that has never been used shows **Never** under **Last used**, which makes unused keys easy to find and revoke. > [!TIP] > If a key leaks, for example into a public repository or a log, revoke it first and then create its replacement. Because every key starts with `evohub_`, secret scanners can be configured to spot it. ## Related - [API overview](https://docs.evohub.io/api-overview.md) - [Errors](https://docs.evohub.io/errors.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) - [Security at EvoHub](https://docs.evohub.io/security.md) --- Source: https://docs.evohub.io/errors.md # Errors When a request fails, the EvoHub API answers with an HTTP status code and a JSON body that says what went wrong in a form your code can act on. This page explains the format and the codes you are most likely to meet. ## The error format Every error body has an `error` object: ```json { "error": { "code": "FORBIDDEN", "message": "insufficient permissions", "request_id": "req_4f1c2a9e7b3d5a6c8e0f1a2b" } } ``` | Field | Meaning | | --- | --- | | `code` | A stable, machine-readable code in capitals. Branch on this. | | `message` | A human-readable explanation. Show or log it, but do not parse it; the wording can change. | | `request_id` | The ID of this request. Present on most errors; the same value is always in the `X-Request-ID` response header. | Validation errors can add a `details` array that names each field that failed: ```json { "error": { "code": "VALIDATION_FAILED", "message": "request validation failed", "details": [ { "field": "email", "message": "email is required" } ] } } ``` ## Status codes | Status | Meaning | What to do | | --- | --- | --- | | **400 Bad Request** | The request is malformed or a field is invalid. | Fix the request. Read `message` and `details`. | | **401 Unauthorized** | No credentials, or the credentials are not valid: the API key is missing, unknown, revoked or expired. | Check the key and the header. Retrying the same request will not help. | | **403 Forbidden** | You are authenticated, but not allowed to do this. | Give the key the scope it needs, or use a person's account where a person is required. | | **404 Not Found** | The resource does not exist, or you are not allowed to know it exists. | Check the ID and which organization or team the key belongs to. | | **409 Conflict** | The request conflicts with the current state, for example a name already taken or a limit reached. | Change the request; it will not succeed as is. | | **429 Too Many Requests** | You went over the rate limit. | Wait for the `Retry-After` seconds, then retry with backoff. | | **500 Internal Server Error** | Something went wrong on EvoHub's side. | Retry later. If it persists, contact support with the request ID. | | **503 Service Unavailable** | EvoHub could not complete the request right now. | Retry shortly with backoff. | ## 401 versus 403 EvoHub keeps these two strictly apart: - **401** always means "we do not know who you are". The credential is missing or not valid. - **403** always means "we know who you are, and you may not do this". A missing scope, an action reserved for administrators, or an action that only a person may take all return 403, never 401. So a 401 is a credential problem, and a 403 is a permission problem. Rotating a key will not fix a 403; changing its scopes or role will. > [!NOTE] > A resource that belongs to a team the key cannot see answers **404**, not 403. Saying "forbidden" would confirm that something with that ID exists. ## Common error codes | Code | Status | Meaning | | --- | --- | --- | | `UNAUTHORIZED` | 401 | No valid credential was sent, or the API key is invalid or expired. The message says which header to send. | | `FORBIDDEN` | 403 | The key or person lacks the permission this endpoint needs. Also returned when you try to give a key or role a permission you do not hold. | | `ADMIN_ONLY` | 403 | Only an administrator can do this, for example giving a key the Admin role. | | `PERSON_REQUIRED` | 403 | This action (such as approving a change request) must be done by a person signed in to EvoHub, not with an API key. | | `API_KEY_NOT_ALLOWED` | 403 | This endpoint is for a signed-in person acting on their own account, such as managing sessions or two-factor authentication. | | `NOT_A_PERSON` | 403 | An organization key tried to do something that must name a person, such as sending an invitation. Use a personal key. | | `VALIDATION_ERROR` | 400 | A field is missing or invalid. The message names it. | | `VALIDATION_FAILED` | 400 | One or more fields are invalid. Each is listed in `details`. | | `INVALID_BODY` | 400 | The request body is not valid JSON. | | `UNKNOWN_PERMISSION` | 400 | A scope or permission name is not one EvoHub knows. | | `RATE_LIMITED` | 429 | Too many requests. See [Rate limits](https://docs.evohub.io/api-overview.md#rate-limits). | | `AUTH_UNAVAILABLE` | 503 | The key could not be checked right now. Your key is fine; retry shortly. | | `INTERNAL_ERROR` | 500 | An unexpected error on EvoHub's side. | Not-found errors often name the resource, for example `MONITOR_NOT_FOUND`. Treat any 404 the same way, whatever its code. ## Request IDs Every response, successful or not, carries an `X-Request-ID` header, and most error bodies repeat it as `request_id`. Log it with every failed call. When you contact support at info@evosync.io, include the request ID and the time of the request; it lets us find that exact call. You can also set your own `X-Request-ID` header on a request, for example your CI job's run ID, and EvoHub uses it instead of generating one. It must be at most 64 characters of letters, digits, `-` and `_`; any other value is replaced with a generated ID. ## Handling errors well - Branch on `code` and the status, not on `message`. - Retry only **429**, **500** and **503**, with exponential backoff. Do not retry **400**, **401**, **403**, **404** or **409** unchanged. - On **401** from a key that used to work, check whether it was revoked or has expired in **My Account → API keys** or **Organization → Organization Keys**. ## Related - [API overview](https://docs.evohub.io/api-overview.md) - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) --- Source: https://docs.evohub.io/on-call-api.md # On-Call API The On-Call API lets scripts and CI jobs do what the On-Call console does: read and answer alerts, raise one from a deploy pipeline, see who is on call, put someone on cover, hold back paging during planned work, and run incidents. Every endpoint, with its parameters, schemas, errors and an example request, is in the [On-Call API reference](https://docs.evohub.io/oncall.md). This page shows the common tasks and the rules that apply to all of them. Paths below are relative to `https://evohub.io`. Requests need an API key, sent as described in [API overview](https://docs.evohub.io/api-overview.md#authentication). > [!NOTE] > Monitoring tools send alerts *into* On-Call with an integration key, not an API key. See the [Alert ingest API reference](https://docs.evohub.io/alert-ingest.md) and [Generic webhook](https://docs.evohub.io/generic-webhook.md). ## Scopes Each endpoint needs one scope. Give a key only the ones its job needs. | To | Scope | | --- | --- | | List and read alerts, their timelines and analytics | `oncall:alert:read` | | Acknowledge, resolve, suppress, assign, take over and add notes to alerts; acknowledge and resolve incidents | `oncall:alert:respond` | | Raise an alert, redirect one to another policy, resolve by fingerprint | `oncall:alert:write` | | Read incidents, their timelines and analytics | `oncall:incident:read` | | Declare and edit incidents, write their timelines, publish them to a status page | `oncall:incident:write` | | Read schedules, overrides and who is on call | `oncall:schedule:read` | | Change schedules, layers, rotations and overrides | `oncall:schedule:write` | | Read escalation policies, and the Slack and Teams connections | `oncall:escalation:read` | | Change escalation policies | `oncall:escalation:write` | | Read integrations (including their ingest keys) | `oncall:integration:read` | | Create, change and delete integrations and their signing secrets | `oncall:integration:write` | | Read maintenance windows | `oncall:maintenance:read` | | Schedule, change and end maintenance windows | `oncall:maintenance:write` | | Read postmortems | `oncall:postmortem:read` | | Write postmortems | `oncall:postmortem:write` | | Organization settings: message templates, Slack and Teams, importing from Opsgenie or PagerDuty | `oncall:settings:write` | | Read the On-Call audit log | `oncall:audit:read` | A key without the scope gets **403**, never 401. See [Errors](https://docs.evohub.io/errors.md#401-versus-403). > [!WARNING] > An integration's record includes its ingest key. A key with `oncall:integration:read` can read every ingest key it can see, so treat it like a credential. ## List open alerts ```bash curl "https://evohub.io/api/v1/alerts?status=open&severity=critical&limit=20" \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` `status=open` means triggered or acknowledged. The list is paged with `limit` (50 by default) and `offset`, and `meta.total` says how many alerts match. Filter by `assignee=me`, `integration_id`, `escalation_policy_id`, `team_id`, a creation range (`created_from`, `created_to`) or free text (`q`), and sort with `sort` and `dir`. ## Acknowledge or resolve an alert ```bash curl -X POST https://evohub.io/api/v1/alerts/$ALERT_ID/acknowledge \ -H "Authorization: Bearer $EVOHUB_API_KEY" curl -X POST https://evohub.io/api/v1/alerts/$ALERT_ID/resolve \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` Both answer with the alert. Doing it twice is harmless: acknowledging an acknowledged alert or resolving a resolved one changes nothing. A resolved alert cannot be acknowledged (**409** `ALERT_RESOLVED`). To answer many at once, send their ids to `/api/v1/alerts/acknowledge-bulk`, `/resolve-bulk` or `/takeover-bulk`; the answer lists which succeeded and which failed. ## Raise an alert from a pipeline ```bash curl -X POST https://evohub.io/api/v1/alerts \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Checkout error budget exhausted", "severity": "high", "fingerprint": "checkout-error-budget", "escalation_policy_id": "'"$POLICY_ID"'" }' ``` This pages people exactly like an alert from an integration, and the notifications count toward usage. While an alert with the same `fingerprint` is open, sending it again does not open a second one. Resolve it later with `POST /api/v1/alerts/resolve-by-fingerprint` and `{"fingerprint": "checkout-error-budget"}`. Both need `oncall:alert:write`. For a monitoring tool that should keep sending alerts, create an integration instead: it gets its own URL and key, and needs no API key. ## Who is on call - `GET /api/v1/on-call-now` — who is on call right now on every schedule. - `GET /api/v1/schedules/{id}/on-call-now` — the same for one schedule, with when the shift ends. - `GET /api/v1/schedules/my-on-call` — for the person the key belongs to: their current shifts and their next one. ## Put someone on cover ```bash curl -X POST https://evohub.io/api/v1/schedules/$SCHEDULE_ID/overrides \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "override_user_id": "'"$USER_ID"'", "start_time": "2026-10-12T18:00:00Z", "end_time": "2026-10-13T09:00:00Z", "reason": "Covering a trip" }' ``` The person must be a member of the organization who can respond to alerts. Someone who is not a member is refused with **404** `NOT_A_MEMBER`, and a member who cannot respond to alerts with **422** `CANNOT_RESPOND`. The same rule applies to anyone you add to a rotation, an escalation step or an alert. See [Overrides and take over](https://docs.evohub.io/overrides-and-takeover.md). ## Hold back paging during planned work ```bash curl -X POST https://evohub.io/api/v1/maintenance-windows \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Database upgrade", "starts_at": "2026-10-12T22:00:00Z", "ends_at": "2026-10-13T00:00:00Z" }' ``` While the window runs, alerts are still recorded but nobody is paged. A window lasts at most 30 days. It can also publish a scheduled maintenance on a status page (`status_page_id`) and silence uptime monitors (`uptime_monitor_ids`). End it early with `POST /api/v1/maintenance-windows/{id}/complete`; deleting it removes it and what it published. ## Run an incident 1. Declare it: `POST /api/v1/incidents` with a `title` and a `severity` (`none`, `minor`, `major` or `critical`). 2. Keep a timeline: `POST /api/v1/incidents/{id}/timeline` with a `message`. 3. Tell customers: `POST /api/v1/incidents/{id}/publish` with a status page's `page_id`. From then on, timeline entries and status changes are posted to the status page as well. 4. Mark it identified and resolved: `POST /api/v1/incidents/{id}/acknowledge`, then `/resolve`. 5. Write the postmortem: `PUT /api/v1/incidents/{id}/postmortem`. To record an incident that is already over, send `started_at` and `resolved_at` when you declare it. ## Rules that apply everywhere - **Teams.** Schedules, escalation policies and integrations can belong to a team. A personal key sees what you see: the organization-wide ones and those of your teams. An organization key sees the organization-wide ones and, when it is scoped to a team, that team's. Anything else answers **404**, as if it did not exist. - **Updates.** `PUT` on an incident, a schedule, an escalation policy or an integration, and `PATCH` on a layer or a maintenance window, keep a field you leave out. An escalation policy keeps its steps unless you send `steps`, which replaces them all. A postmortem is replaced whole. - **Personal endpoints.** Notification methods, the notification schedule and `my-on-call` belong to the caller. With an organization key, the key itself is the caller. - **Streams.** `/api/v1/alerts/sse` and `/api/v1/incidents/{id}/sse` are Server-Sent Events streams of changes as they happen. Events are not replayed; read the list again after reconnecting. ## Related - [On-Call API reference](https://docs.evohub.io/oncall.md) - [Alert ingest API reference](https://docs.evohub.io/alert-ingest.md) - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) - [Errors](https://docs.evohub.io/errors.md) - [On-Call overview](https://docs.evohub.io/on-call-overview.md) --- Source: https://docs.evohub.io/uptime-api.md # Uptime API The Uptime API lets scripts and CI jobs do what the Uptime console does: create and change monitors, pause them around a deploy, read their check history and uptime, silence alerts for planned work, and decide where alerts go. Every endpoint, with its parameters, schemas, errors and an example request, is in the [Uptime API reference](https://docs.evohub.io/uptime.md). This page shows the common tasks and the rules that apply to all of them. Paths below are relative to `https://evohub.io`. Requests need an API key, sent as described in [API overview](https://docs.evohub.io/api-overview.md#authentication). ## Scopes Each endpoint needs one scope. Give a key only the ones its job needs. | To | Scope | | --- | --- | | List and read monitors, checks, uptime, analytics and the weekly summary settings | `uptime:monitor:read` | | Create, change, pause, resume and delete monitors | `uptime:monitor:write` | | Read a monitor's outages | `uptime:incident:read` | | Read silences | `uptime:silence:read` | | Silence the organization or a monitor, and lift it | `uptime:silence:write` | | Read notification channels and which monitors use them | `uptime:channel:read` | | Create and delete channels, link and unlink them | `uptime:channel:write` | | Read the Uptime audit log | `uptime:audit:read` | A key without the scope gets **403**, never 401. See [Errors](https://docs.evohub.io/errors.md#401-versus-403). ## Create a monitor ```bash curl https://evohub.io/api/v1/monitors \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Checkout API", "type": "http", "url": "https://api.acme.example/health", "interval_seconds": 60, "timeout_seconds": 10, "is_active": true, "sla_target": 99.9 }' ``` The answer is the monitor, with its `id`. `name`, `type`, `interval_seconds` and `timeout_seconds` are required. A monitor created without `"is_active": true` starts paused. The `type` decides what `url` must be: an `http(s)` URL for `http` and `keyword`, `host:port` for `tcp`, a host for `icmp`, and nothing for `heartbeat`. Assertions, slow-response thresholds and SLA targets are described in [Uptime monitoring](https://docs.evohub.io/uptime-overview.md). ## Pause and resume There is no separate pause endpoint: change `is_active`. ```bash # Pause before a deploy curl -X PATCH https://evohub.io/api/v1/monitors/$MONITOR_ID \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"is_active": false}' # Resume afterwards curl -X PATCH https://evohub.io/api/v1/monitors/$MONITOR_ID \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"is_active": true}' ``` A paused monitor is not checked and raises no alerts. `PATCH` changes only the fields you send, so the same call can change anything else about a monitor. ## Silence a monitor for planned work If you want the monitor to keep being checked but not page anyone, give it a silence window instead of pausing it. It needs `uptime:silence:write`. ```bash curl -X PUT https://evohub.io/api/v1/monitors/$MONITOR_ID/silence \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"starts_at": "2026-10-12T22:00:00Z", "ends_at": "2026-10-13T00:00:00Z"}' ``` A monitor has one window; setting a new one replaces it, and `DELETE` on the same path removes it. To mute every monitor of the organization for a while, `PUT /api/v1/silence` with `{"minutes": 60}`. See [Alerts and silence windows](https://docs.evohub.io/uptime-alerts-and-silence-windows.md). ## Read uptime and SLA - `GET /api/v1/monitors/{id}` returns the monitor with `uptime_percent` for `24h`, `7d`, `30d` and `90d`, and `sla_breached_30d` when it has an SLA target. - `GET /api/v1/monitors/{id}/uptime/daily?days=90` returns one entry per UTC day for an uptime calendar. - `GET /api/v1/uptime/analytics?from=2026-09-01&to=2026-09-30` returns uptime, latency, outages and SLA results for every monitor you can see, with a time series and a row per monitor and team. ## Heartbeat monitors A heartbeat monitor's job calls its ping URL, `https://evohub.io/ping/{ping_token}`. The `ping_token` is in the monitor returned when you create it. The ping URL does not take an API key — the token is the credential — and it is not rate limited. See [Heartbeat monitors](https://docs.evohub.io/heartbeat-monitors.md). ## Notification channels Create a channel with `POST /api/v1/notification-channels`, then link it to a monitor with `POST /api/v1/monitors/{id}/channels/{channelId}`. For a Slack or chat app channel, list the choices with `GET /api/v1/notification-channels/chat-apps` and create a `chat` channel with the `id` you pick. A webhook channel's URL and secret are write-only. Reads show only the host, a short hint of the path and whether a secret is set: ```json { "id": "nch_6a9d2f4b-8c1e-4b3a-9f7d-5e0c2a8b4d16", "name": "Incident bridge", "type": "webhook", "config": { "url_host": "hooks.acme.example", "url_hint": "…/5b1d", "has_secret": true } } ``` To change a webhook's URL, create a new channel and delete the old one. ## Things to know - **Teams.** A personal key sees what you see: the organization-wide monitors and your teams' monitors. An organization key sees the organization-wide monitors and, when it is scoped to a team, that team's. A monitor it cannot see answers **404** `MONITOR_NOT_FOUND`, the same as one that does not exist. - **Monitor credentials are write-only.** A monitor's Basic Auth password and header values are never returned. Reads show `has_http_auth_password` and `http_headers` as `[{"name": "Authorization", "has_value": true}]`. On `PATCH`, leave `http_auth_password` out to keep it (send `""` to remove it). Send a header with an empty value to keep its stored value. The list you read can be sent back as it is. - **Credentials in a monitor URL are masked.** Reads show `https://user:pass@…` as `https://****:****@…` and the value of a `token`, `key`, `secret`, `password`, `sig`, `signature`, `auth`, `api_key` or `access_token` query parameter as `****`. Checks use the URL as you entered it. The masked URL can be sent back on `PATCH`: each `****` keeps the stored value, so you can change the path or another parameter without re-entering the credentials. - **Empty lists.** The monitor, check, outage and audit log lists answer `{"data": []}` when there is nothing to list. - **Weekly summary.** A key can read the weekly summary settings and change its owner's own subscription, but turning the summary on or off and sending a preview need an owner or administrator signed in to the console. - **Errors.** Branch on `error.code`. The codes each endpoint returns are listed in the reference; the general rules are in [Errors](https://docs.evohub.io/errors.md). ## Related - [Uptime API reference](https://docs.evohub.io/uptime.md) - [API overview](https://docs.evohub.io/api-overview.md) - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) - [Uptime monitoring](https://docs.evohub.io/uptime-overview.md) --- Source: https://docs.evohub.io/status-api.md # Status API The Status API lets scripts and CI jobs do what the Status Pages console does: create pages and components, set a component's status by hand, post incidents and scheduled maintenance with their updates, look after subscribers, connect a custom domain and read visitor analytics. Every endpoint, with its parameters, schemas, errors and an example request, is in the [Status API reference](https://docs.evohub.io/status.md). This page shows the common tasks and the rules that apply to all of them. Paths below are relative to `https://evohub.io`. Requests need an API key, sent as described in [API overview](https://docs.evohub.io/api-overview.md#authentication), except the public page reads at the end. > [!NOTE] > In the console, incidents and maintenance on a status page are run from On-Call, which publishes them for you. Use the [On-Call API](https://docs.evohub.io/on-call-api.md) for that flow. The endpoints here write to the page directly, for teams that run their status page on its own. ## Scopes Each endpoint needs one scope. Give a key only the ones its job needs. | To | Scope | | --- | --- | | List and read pages, their domain, logos and analytics | `status:page:read` | | Create, change and delete pages; theme, header, layout, logos, domain, analytics settings | `status:page:write` | | Read components | `status:component:read` | | Add, change, reorder, rename groups of, set the status of and delete components | `status:component:write` | | Read incidents | `status:incident:read` | | Create incidents, post and edit their updates, write postmortems | `status:incident:write` | | Read maintenance | `status:maintenance:read` | | Schedule maintenance and post its updates | `status:maintenance:write` | | Read subscribers | `status:subscriber:read` | | Remove subscribers, change subscription settings | `status:subscriber:write` | | Read the Status audit log | `status:audit:read` | A key without the scope gets **403**, never 401. See [Errors](https://docs.evohub.io/errors.md#401-versus-403). ## Set a component's status ```bash curl -X PATCH https://evohub.io/api/v1/components/$COMPONENT_ID/status \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"current_status": "degraded"}' ``` The status is one of `operational`, `degraded`, `partial_outage`, `major_outage` and `maintenance`. A component linked to an Uptime monitor (`source: uptime_monitor`) keeps showing its monitor's live state, whatever you set. List a page's components, with their ids, with `GET /api/v1/pages/{pageID}/components`. ## Post an incident ```bash curl -X POST https://evohub.io/api/v1/pages/$PAGE_ID/incidents \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Elevated API error rates", "impact": "major", "affected_component_ids": ["'"$COMPONENT_ID"'"], "body": "We are looking into elevated error rates on the API.", "managed_in": "status_page" }' ``` `impact` is `none`, `minor`, `major` or `critical`; it decides how the affected components show while the incident is open. With a `body`, confirmed subscribers who follow an affected component, or the whole page, get an email. Then keep the timeline going and close it: ```bash curl -X POST https://evohub.io/api/v1/pages/$PAGE_ID/incidents/$INCIDENT_ID/updates \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status": "resolved", "body": "Error rates are back to normal."}' ``` Each update moves the incident to its `status` (`investigating`, `identified`, `monitoring` or `resolved`) and mails subscribers. Editing the incident with `PATCH` changes its title, impact or status without an update and without mail. After it is resolved, publish a postmortem with `PUT …/incidents/{id}/postmortem` and `{"body": "…", "published": true}`. To record an incident that is already over, send `started_at`, `resolved_at` and `resolution_body` when you create it. It lands on the days it happened and mails nobody. ## Schedule maintenance ```bash curl -X POST https://evohub.io/api/v1/pages/$PAGE_ID/maintenances \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Database upgrade", "body": "Expect up to five minutes of read-only mode.", "scheduled_start": "2026-10-12T22:00:00Z", "scheduled_end": "2026-10-13T00:00:00Z", "affected_component_ids": ["'"$COMPONENT_ID"'"], "managed_in": "status_page" }' ``` Post `in_progress` and `completed` updates to `…/maintenances/{id}/updates` as the work goes. Setting the window to `completed` with `PATCH` also closes its timeline with "This maintenance has been completed." ## Connect a custom domain ```bash curl -X POST https://evohub.io/api/v1/pages/$PAGE_ID/domain \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "status.example.com"}' ``` Use a subdomain; an apex domain such as `example.com` is refused. Create the CNAME records the answer lists at your DNS provider, then poll `GET /api/v1/pages/{pageID}/domain` until `status` is `active`. See [Custom domain](https://docs.evohub.io/status-page-custom-domain.md). ## Read visitor analytics `GET /api/v1/pages/{pageID}/analytics?from=2026-09-01&to=2026-09-30` returns views, visitors, feed and AI reads per day and the top routes, referrers, countries and devices. `GET /api/v1/pages/analytics` does the same across every page the key can see, with subscriber growth and incidents published. Download one report as CSV from `…/analytics/export.csv?report=referrers`. ## Rules that apply everywhere - **Teams.** A page belongs to the whole organization or to one team. A personal key sees what you see: the organization-wide pages and your teams' pages. An organization key sees the organization-wide pages and, when it is scoped to a team, that team's. A page the key cannot see, and everything under it, answers **404**, as if it did not exist. - **Strict bodies.** A field the endpoint does not take is refused with **400** `INVALID_BODY`, so a typo cannot be silently ignored. - **Updates.** A field you leave out keeps its value, on `PUT` of a page as on every `PATCH`. `null` clears a nullable field. A page's `slug` and `team_id` do not change through `PUT`: a different value is refused with `VALIDATION_ERROR`. - **Errors.** This service answers a bad field with `VALIDATION_ERROR` and the field in the message (a bad analytics range can also come back as `VALIDATION_FAILED` with `details`), and an unexpected failure with `INTERNAL_ERROR`. - **Public reads.** `GET /api/v1/public/pages/{pageID}` (or `/by-domain/{domain}`) and their `feed.rss` and `feed.atom` need no key and return only what visitors see. A private page answers 404. ## Related - [Status API reference](https://docs.evohub.io/status.md) - [On-Call API](https://docs.evohub.io/on-call-api.md) - [Status Pages overview](https://docs.evohub.io/status-pages-overview.md) - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) - [Errors](https://docs.evohub.io/errors.md) --- Source: https://docs.evohub.io/evotrail-api.md # EvoTrail API EvoTrail is your organization's audit trail, usage and metrics across every EvoHub product. Its API lets a script or a SIEM pull the audit trail, export it as CSV, follow usage by product and team, and read each product's metrics through one prefix. Everything is read-only. Every endpoint, with its parameters, schemas, errors and an example request, is in the [EvoTrail API reference](https://docs.evohub.io/evotrail.md). Paths below are relative to `https://evohub.io`. Requests need an API key, sent as described in [API overview](https://docs.evohub.io/api-overview.md#authentication). ## Scopes EvoTrail checks two things: its own scope, and the scope of each product whose data it returns. | To | EvoTrail scope | And, for each product | | --- | --- | --- | | Search, count and export the audit trail | `evotrail:audit:read` | The product's audit scope, for example `oncall:audit:read`, `uptime:audit:read`, `status:audit:read` or `identity:audit:read` for the organization's own events | | Read usage, the overview and the retention periods | `evotrail:metrics:read` | One of the product's read scopes, for example `oncall:alert:read` or `uptime:monitor:read` | | Read a product's metrics under `/api/v1/evotrail/metrics/…` | `evotrail:metrics:read` | The product's read scope, for example `status:page:read` | A product the key cannot read is simply left out of the answer. Asking only for products the key cannot read gets **403**, never 401. See [Errors](https://docs.evohub.io/errors.md#401-versus-403). > [!NOTE] > Per-person usage (`/api/v1/evotrail/usage/people`, and `actor_id` on `/usage`) is for organization owners and administrators in the console. An API key never counts as one, so it always gets 403 there. The audit trail itself still names who did each thing. ## Pull the audit trail ```bash curl "https://evohub.io/api/v1/evotrail/audit?product=organization&action=member.*&from=2026-10-01&limit=200" \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` Events come newest first, 50 per page by default and at most 200. While `has_more` is true, send the answer's `next_cursor` back as `cursor` for the next page. Filters combine: `product` (repeat it for several), `actor_id`, `actor_type`, `action` (exact, or a prefix ending in `*`), `resource_type`, `resource_id`, `team_id`, `request_id`, `ip` and `class` (`audit` for changes to configuration and access, `activity` for day-to-day work). List rows leave out the change itself. `GET /api/v1/evotrail/audit/{id}` returns one event with `before`, `after` and `metadata`. To keep your own copy, poll with `from` set to the last `occurred_at` you stored. EvoTrail copies each product's events every few seconds, but a product can lag behind, so leave an overlap of a few minutes and skip ids you already have. ## Export as CSV ```bash curl -o audit.csv "https://evohub.io/api/v1/evotrail/audit/export.csv?from=2026-09-01&to=2026-09-30" \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` The export takes the same filters and carries up to 100,000 events; more than that is refused with **400** `EXPORT_TOO_LARGE`, so narrow the range or the filters. Each export is recorded in the trail itself, as `audit.exported`. ## See usage `GET /api/v1/evotrail/usage` returns, per product, the actions and active people in the range against the previous period, a series (hourly for 48 hours or less), and the top 10 actions and teams. `GET /api/v1/evotrail/overview` adds each product's own numbers, such as `page_views` for Status. Every number carries a `drill`: the audit-trail filters that list the events behind it. ## Read a product's metrics Each product's analytics can be read through EvoTrail with one scope set, for example: ```bash curl "https://evohub.io/api/v1/evotrail/metrics/uptime?from=2026-09-01&to=2026-09-30" \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` EvoTrail passes your query on and returns the product's answer unchanged, so the shape is the one in that product's reference: `/metrics/oncall/alerts` is On-Call's `GET /api/v1/alerts/analytics`, `/metrics/uptime` is Uptime's `GET /api/v1/uptime/analytics`, `/metrics/status` is Status's `GET /api/v1/pages/analytics`. If the product is slow or down you get **502** or **504**. ## Rules that apply everywhere - **Ranges.** `from` and `to` take a UTC date (`YYYY-MM-DD`, `to` inclusive) or an RFC 3339 timestamp. The default is the last 30 days; the longest is 366 days. - **Retention is fixed.** Audit events are kept 365 days, activity events 90 days, IP addresses and user agents 90 days, for every organization. `GET /api/v1/evotrail/settings` returns these numbers. - **Free.** Reading EvoTrail is never metered. ## Related - [EvoTrail API reference](https://docs.evohub.io/evotrail.md) - [EvoTrail overview](https://docs.evohub.io/evotrail-overview.md) and [Audit Logs](https://docs.evohub.io/evotrail-audit-logs.md) in the console - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) - [Errors](https://docs.evohub.io/errors.md) - [Organization API](https://docs.evohub.io/organization-api.md) --- Source: https://docs.evohub.io/organization-api.md # Organization API The Organization API lets a script keep EvoHub in step with your directory or HR system: list the people in your organization, create teams and fill them, define custom roles and give them to people or teams, invite new colleagues and read the organization's audit log. Every endpoint, with its parameters, schemas, errors and an example request, is in the [Organization API reference](https://docs.evohub.io/organization.md). This page shows the common tasks and the rules that apply to all of them. Paths below are relative to `https://evohub.io`. Requests need an API key, sent as described in [API overview](https://docs.evohub.io/api-overview.md#authentication). ## Scopes Each endpoint needs one scope. Give a key only the ones its job needs. | To | Scope | | --- | --- | | List members and read one | `identity:user:read` | | Rename members, change their title, remove them from the organization | `identity:user:write` | | Send, resend, list and cancel invitations | `identity:user:invite` | | List every team and read any team's members and roles | `identity:team:read` | | Create, rename and delete teams, add and remove their members | `identity:team:write` | | List roles, who holds them, and a member's roles | `identity:role:read` | | Create, change and delete roles, give and take them | `identity:role:write` | | Rename the organization | `identity:org:write` | | Read the organization's audit log | `identity:audit:read` | `GET /api/v1/users/me`, `GET /api/v1/organizations` and `GET /api/v1/permissions` need no scope. A key without the scope gets **403**, never 401. See [Errors](https://docs.evohub.io/errors.md#401-versus-403). ## What a key cannot do A key is held to its scopes, and it never counts as an owner or administrator, whoever created it. So: - **It cannot hand out more than it holds.** A role, an invitation or a seat in a team that would give someone a permission the key does not hold is refused with **403** `FORBIDDEN`, and the message names the permission. - **It cannot do what only administrators do**: give or take the Admin role or standing, edit an owner or administrator, edit the default roles. These return **403** `ADMIN_ONLY`. - **Some things are for a person in the console**: signing in, everything about a person's own account (profile, password, two-factor authentication, sessions, devices, the list of their organizations, switching or creating organizations, seeing and accepting their invitations), creating and revoking API keys, enrolling agents, the login policy and deleting the organization. A key gets **403** there. See [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md#what-a-key-can-never-do). - **An organization key is not a person.** It cannot send invitations (**403** `NOT_A_PERSON`); use a personal key. `GET /api/v1/users/me` answers it with **403** `NOT_A_PERSON` too, which means the key works but has no profile. Chat apps (Slack and Microsoft Teams) are connected in On-Call: see the [On-Call API](https://docs.evohub.io/on-call-api.md). ## List members ```bash curl https://evohub.io/api/v1/users \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` Each member has a `role`, their standing in the organization: `owner`, `admin`, `member` or `viewer`. Add `?permission=oncall:alert:respond` to get only the people who hold a permission, for example to know who can be put on a rota. ## Keep a team in step with your directory ```bash # Create the team once. curl -X POST https://evohub.io/api/v1/teams \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Platform", "slug": "platform"}' # Then add and remove people as your directory changes. curl -X POST https://evohub.io/api/v1/teams/$TEAM_ID/members \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"user_id": "'"$USER_ID"'", "role": "member"}' curl -X DELETE https://evohub.io/api/v1/teams/$TEAM_ID/members/$USER_ID \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` Adding someone who is already in the team only changes their place in it (`admin`, `member` or `viewer`). The person must already be a member of the organization; invite them first if not. ## Give access through a role ```bash curl -X POST https://evohub.io/api/v1/roles \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Incident responder", "permissions": ["oncall:alert:read", "oncall:alert:respond", "oncall:incident:write"] }' ``` Give the role to a team with `POST /api/v1/roles/{id}/teams` and `{"team_id": "…"}`: everyone in the team holds it for as long as they are in it, so access follows your directory. Give it to one person with `POST /api/v1/roles/{id}/users` and `{"user_id": "…"}`. `GET /api/v1/permissions` lists every permission name a role can carry. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). ## Invite someone ```bash curl -X POST https://evohub.io/api/v1/invitations \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "deniz@acme.example", "role": "member", "team_ids": ["'"$TEAM_ID"'"]}' ``` The invitation is emailed and lasts seven days. `role_ids` and `team_ids` are what the person gets when they accept. An address that already has a pending invitation gets **409** `INVITATION_PENDING`: resend it with `POST /api/v1/invitations/{id}/resend` instead. ## Read the audit log `GET /api/v1/audit-logs?limit=200` lists who changed what in the organization, newest first, including sign-ins. Page with `offset`, and leave out noisy actions with `exclude=user.login,user.logout`. To read every product's audit trail in one place, use the [EvoTrail API](https://docs.evohub.io/evotrail-api.md). ## Rules that apply everywhere - **Updates.** `PATCH` on a member, a team or the organization keeps a field you leave out; `""` clears a title or description, and a name (or the organization's slug) cannot be sent empty. A slug another organization uses is **409 `SLUG_TAKEN`**. `PATCH` on a role replaces its name and description, and its permissions when you send them. - **Personal keys and `me`.** `GET /api/v1/users/me` returns the person a personal key belongs to. Its `permissions` are what the person holds, not the key's scopes. - **Errors.** This service answers a bad field with `VALIDATION_ERROR` and the field in the message, or with `VALIDATION_FAILED` and each field in `details`, and an unexpected failure with `INTERNAL_ERROR`. Its error bodies carry `request_id`. ## Related - [Organization API reference](https://docs.evohub.io/organization.md) - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) - [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md) - [Errors](https://docs.evohub.io/errors.md) --- Source: https://docs.evohub.io/catalog-api.md # Catalog API The Catalog API lets scripts and CI jobs do what the Catalog console does: declare services and keep them current, read the inventory, decide which service a resource belongs to, read drift and SLA reports, and export everything for an audit. Every endpoint, with its parameters, schemas and errors, is in the [Catalog API reference](https://docs.evohub.io/catalog.md). This page shows the common tasks and the rules that apply to all of them. Paths below are relative to `https://evohub.io`, and every one starts with `/api/v1/catalog/`. Requests need an API key, sent as described in [API overview](https://docs.evohub.io/api-overview.md#authentication). ## Scopes | To | Scope | | --- | --- | | Read services, tiers and SLA reports | `catalog:component:read` | | Declare, change and delete services, set their RACI, manage tiers | `catalog:component:write` | | Read resources, changes, applications, images and the map | `catalog:resource:read` | | Accept and reject matches, put a resource or an application in a service | `catalog:resource:write` | | Read environments and sources | `catalog:source:read` | | Manage environments, rename, pause and resume sources | `catalog:source:write` | | Read cloud accounts | `identity:cloudaccount:read` | | Connect and manage cloud accounts | `identity:cloudaccount:write` (connecting also needs `catalog:source:write`) | The match queue, drift, a service's resources and the inventory export need both `catalog:component:read` and `catalog:resource:read`. A key without a scope gets **403**, never 401. See [Catalog permissions](https://docs.evohub.io/catalog-permissions.md). ## Declare a service ```bash curl https://evohub.io/api/v1/catalog/components \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "slug": "payments-api", "display_name": "Payments API", "owner_team_id": "team_3b6f0e2a-1c4d-4e8f-9a7b-2d5c6e8f0a1b", "tier": 1, "runbook_url": "https://wiki.acme.example/runbooks/payments" }' ``` `slug` and `display_name` are required; the slug can never change. Every endpoint that takes a service accepts its ID (`cmp_…`) or its slug, so `GET /api/v1/catalog/components/payments-api` works. `PATCH` changes only the fields you send; send `null` to clear `owner_team_id`, `tier` or `sla_overrides`, and `""` to clear a text field. A pipeline can keep services in step with a repository: `PATCH` each one, and `POST` it when the answer is 404. ## Find what nobody owns ```bash curl "https://evohub.io/api/v1/catalog/resources?unowned=true&exclude_kind=Namespace,Node" \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` Lists page with `cursor`: pass the previous answer's `pagination.next_cursor` until `has_more` is false. The first page of the resource and application lists also carries `pagination.total`. Other useful filters: `image=nginx` (any tag of a repository), `floating=true` (images with no fixed version), `component_id`, `environment_id` and `status`. `GET /api/v1/catalog/drift` returns the whole drift report in one answer. ## Decide the match queue ```bash # What is waiting curl https://evohub.io/api/v1/catalog/matches \ -H "Authorization: Bearer $EVOHUB_API_KEY" # Accept one curl -X POST https://evohub.io/api/v1/catalog/matches/$MATCH_ID/accept \ -H "Authorization: Bearer $EVOHUB_API_KEY" # Or put an application in a service directly curl -X POST https://evohub.io/api/v1/catalog/applications/$APP_ID/component \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"component": "payments-api"}' ``` A rejected proposal is never proposed again. `{"component": null}` unbinds. See [Matching and drift](https://docs.evohub.io/catalog-matching-and-drift.md). ## Read an SLA report ```bash curl "https://evohub.io/api/v1/catalog/components/payments-api/sla-report?month=2026-10" \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` Without `month`, the current month so far. Each part's `status` is `measured`, `not_connected` or `unavailable`. ## Export the inventory `GET /api/v1/catalog/exports/inventory` returns every asset not gone, with its application, service, owner team, tier, availability target and RACI: at most 50,000 rows, with `truncated` when there were more. Teams and people are their IDs. ## Cloud accounts Connections are created first and given access second: `POST /api/v1/catalog/connections` returns the connection with its `external_id`; then, for AWS, download the role template from `GET /api/v1/catalog/connections/{id}/template`, create the role, and `PATCH` its `role_arn`. `POST /api/v1/catalog/connections/{id}/check` tries the access and answers `{"ok": true, "account_id": "…"}` or `{"ok": false, "error": "…"}`. See [Cloud accounts](https://docs.evohub.io/catalog-cloud-accounts.md). ## Things to know - **Agents only.** The routes under `/api/v1/catalog/ingest/` are for the EvoHub agent, which signs each request with its own key. An API key cannot send inventory, and an agent cannot read. - **Observations are read-only.** Resources and applications are never created or edited through the API; only which service they belong to is decided here. Sources have no create or delete either: an agent's appears when it first reports, and is retired by revoking the agent. - **Parts you cannot read are left out.** A service's detail omits `observations` for a key without `catalog:resource:read`; a resource's or application's omits `proposals` without `catalog:component:read`; a cloud account's `external_id` is empty without `identity:cloudaccount:write`. - **Strict bodies.** A field an endpoint does not know is refused with `INVALID_BODY`, so a typo never reads as success. - **Errors.** Branch on `error.code`, for example `NOT_FOUND`, `SLUG_TAKEN`, `SLUG_IMMUTABLE`, `MATCH_DECIDED` or `ENVIRONMENT_IN_USE`. The general rules are in [Errors](https://docs.evohub.io/errors.md). ## Related - [Catalog API reference](https://docs.evohub.io/catalog.md) - [API overview](https://docs.evohub.io/api-overview.md) - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) - [Catalog overview](https://docs.evohub.io/catalog-overview.md) --- Source: https://docs.evohub.io/docs-and-changelog-api.md # Docs and Changelog API Scripts and CI jobs can do most of what the Docs and Changelog consoles do: create and configure sites, write and publish pages and entries, upload files, manage domains, read analytics and the activity log. A full API reference for these two products is not published yet. The endpoints on this page are stable and work with an API key today; this page lists them by task, with the scope each one needs. Paths below are relative to `https://evohub.io`. Requests need an API key, sent as described in [API overview](https://docs.evohub.io/api-overview.md#authentication). Answers use the same envelope as the rest of the API: `{"data": ...}` on success and an `error` object on failure. See [Errors](https://docs.evohub.io/errors.md). ## Scopes Each endpoint needs one scope. Give a key only the ones its job needs. | To | Docs | Changelog | | --- | --- | --- | | Read sites and everything on them: pages or entries, revisions, analytics, feedback, activity | `docs:site:read` | `changelog:site:read` | | Create and configure sites: settings, domain, logo, access, versions or categories, redirects, GitHub repository, integrations | `docs:site:write` | `changelog:site:write` | | Delete a site | `docs:site:delete` | `changelog:site:delete` | | Write: pages and sections, or entries; files; imports; opening change requests or submitting for review | `docs:page:write` | `changelog:entry:write` | | Put writing live: publish, unpublish, schedule | `docs:page:publish` | `changelog:entry:publish` | The activity log of a site, and of the organization, is read with the site read scope. A key without the scope gets **403** `FORBIDDEN`, never 401. A key sees organization-wide sites and, for a personal key, your teams' sites; an organization key scoped to a team sees that team's. A site it cannot see answers **404**, the same as one that does not exist. See [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md). ## Docs ### Sites | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/docs/sites` | `docs:site:read` | List the sites the key can see | | GET | `/api/v1/docs/sites/overview` | `docs:site:read` | The list's figures for every site at once | | POST | `/api/v1/docs/sites` | `docs:site:write` | Create a site | | GET | `/api/v1/docs/templates` | `docs:site:read` | List the site templates | | POST | `/api/v1/docs/sites/from-template` | `docs:site:write` | Create a site from a template | | GET | `/api/v1/docs/starter.zip` | `docs:site:read` | Download the starter project | | GET | `/api/v1/docs/sites/{siteID}` | `docs:site:read` | Read a site and its settings | | PATCH | `/api/v1/docs/sites/{siteID}` | `docs:site:write` | Change a site's settings; only the fields you send change | | DELETE | `/api/v1/docs/sites/{siteID}` | `docs:site:delete` | Delete a site with all its pages and files | | POST | `/api/v1/docs/sites/{siteID}/publish` | `docs:site:write` | Put the site online or take it offline: `{"published": true}` | | POST | `/api/v1/docs/sites/{siteID}/preview` | `docs:site:read` | Create a private preview link of the live site | | POST | `/api/v1/docs/sites/{siteID}/logo` | `docs:site:write` | Upload the logo | | DELETE | `/api/v1/docs/sites/{siteID}/logo` | `docs:site:write` | Remove the logo | | POST | `/api/v1/docs/sites/{siteID}/logo/dark` | `docs:site:write` | Upload the dark-mode logo | | DELETE | `/api/v1/docs/sites/{siteID}/logo/dark` | `docs:site:write` | Remove the dark-mode logo | | GET | `/api/v1/docs/sites/{siteID}/export` | `docs:site:read` | Download the whole site as a zip | ### Versions and languages | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/docs/sites/{siteID}/versions` | `docs:site:read` | List the site's versions | | POST | `/api/v1/docs/sites/{siteID}/versions` | `docs:site:write` | Create a version | | PATCH | `/api/v1/docs/versions/{versionID}` | `docs:site:write` | Change a version | | DELETE | `/api/v1/docs/versions/{versionID}` | `docs:site:write` | Delete a version | | GET | `/api/v1/docs/sites/{siteID}/translations` | `docs:site:read` | How far each language's translation has come | | GET | `/api/v1/docs/pages/{pageID}/translations` | `docs:site:read` | List a page's translations | | POST | `/api/v1/docs/pages/{pageID}/translations` | `docs:page:write` | Start a translation of a page | ### Sections and pages | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/docs/sites/{siteID}/tree` | `docs:site:read` | The navigation: sections and pages in order | | PUT | `/api/v1/docs/sites/{siteID}/order` | `docs:page:write` | Reorder the navigation, and move pages between sections | | POST | `/api/v1/docs/sites/{siteID}/sections` | `docs:page:write` | Create a section | | PATCH | `/api/v1/docs/sections/{sectionID}` | `docs:page:write` | Rename a section | | DELETE | `/api/v1/docs/sections/{sectionID}` | `docs:page:write` | Delete a section | | POST | `/api/v1/docs/sites/{siteID}/pages` | `docs:page:write` | Create a page; it starts as a draft | | GET | `/api/v1/docs/pages/{pageID}` | `docs:site:read` | Read a page: its draft and what is live | | PATCH | `/api/v1/docs/pages/{pageID}` | `docs:page:write` | Edit a page's draft | | DELETE | `/api/v1/docs/pages/{pageID}` | `docs:page:write` | Delete a page; a published one also needs `docs:page:publish` | | POST | `/api/v1/docs/pages/{pageID}/git/resolve` | `docs:page:write` | Settle a conflict between the page and its GitHub file | ### Publishing | Method | Path | Scope | What it does | | --- | --- | --- | --- | | POST | `/api/v1/docs/pages/{pageID}/publish` | `docs:page:publish` | Publish the page's draft | | POST | `/api/v1/docs/pages/{pageID}/unpublish` | `docs:page:publish` | Take the page off the live site | On a site that requires review, publishing goes through change requests and these answer **409** `REVIEW_REQUIRED`. Open a change request instead (below); a person approves and merges it. ### Revisions | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/docs/pages/{pageID}/revisions` | `docs:site:read` | List a page's revisions, newest first | | GET | `/api/v1/docs/pages/{pageID}/revisions/{revisionID}` | `docs:site:read` | Read one revision | | POST | `/api/v1/docs/pages/{pageID}/revisions/{revisionID}/restore` | `docs:page:write` | Put a revision back into the draft | | POST | `/api/v1/docs/pages/{pageID}/revisions/{revisionID}/rollback` | `docs:page:write` | Make an earlier publish live again; also needs `docs:page:publish`, except on a site that requires review, where it opens a change request instead | ### Change requests | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/docs/sites/{siteID}/changes` | `docs:site:read` | What is waiting to go live | | GET | `/api/v1/docs/sites/{siteID}/change-requests` | `docs:site:read` | List change requests | | POST | `/api/v1/docs/sites/{siteID}/change-requests` | `docs:page:write` | Open a change request | | GET | `/api/v1/docs/change-requests/{crID}` | `docs:site:read` | Read a change request | | PATCH | `/api/v1/docs/change-requests/{crID}` | `docs:page:write` | Change its title, description, items or reviewers | | GET | `/api/v1/docs/change-requests/{crID}/diff` | `docs:site:read` | What it changes, against the live site | | POST | `/api/v1/docs/change-requests/{crID}/close` | `docs:page:write` | Close it without merging | | POST | `/api/v1/docs/change-requests/{crID}/reopen` | `docs:page:write` | Reopen a closed one | | POST | `/api/v1/docs/change-requests/{crID}/refresh` | `docs:page:write` | Bring its items up to date with their drafts | | GET | `/api/v1/docs/change-requests/{crID}/comments` | `docs:site:read` | List its comments | | POST | `/api/v1/docs/change-requests/{crID}/comments` | `docs:page:write` or `docs:page:publish` | Comment on it | | DELETE | `/api/v1/docs/comments/{commentID}` | `docs:site:read` | Delete one of the key's own comments | | POST | `/api/v1/docs/change-requests/{crID}/preview` | `docs:page:write` | Create a preview link of the change request | | DELETE | `/api/v1/docs/change-requests/{crID}/preview` | `docs:page:write` | Withdraw its preview link | ### Snippets and API references | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/docs/sites/{siteID}/snippets` | `docs:site:read` | List reusable snippets | | POST | `/api/v1/docs/sites/{siteID}/snippets` | `docs:page:write` | Create a snippet | | GET | `/api/v1/docs/snippets/{snippetID}` | `docs:site:read` | Read a snippet | | PATCH | `/api/v1/docs/snippets/{snippetID}` | `docs:page:write` | Edit a snippet's draft | | DELETE | `/api/v1/docs/snippets/{snippetID}` | `docs:page:write` | Delete a snippet | | POST | `/api/v1/docs/snippets/{snippetID}/publish` | `docs:page:publish` | Publish a snippet | | POST | `/api/v1/docs/snippets/{snippetID}/unpublish` | `docs:page:publish` | Unpublish a snippet | | GET | `/api/v1/docs/sites/{siteID}/api-specs` | `docs:site:read` | List API references | | POST | `/api/v1/docs/sites/{siteID}/api-specs` | `docs:page:write` | Add an API reference from an OpenAPI document | | GET | `/api/v1/docs/api-specs/{specID}` | `docs:site:read` | Read an API reference | | PATCH | `/api/v1/docs/api-specs/{specID}` | `docs:page:write` | Change an API reference's settings | | DELETE | `/api/v1/docs/api-specs/{specID}` | `docs:page:write` | Delete an API reference | | GET | `/api/v1/docs/api-specs/{specID}/file` | `docs:site:read` | Download its OpenAPI document | | PUT | `/api/v1/docs/api-specs/{specID}/file` | `docs:page:write` | Replace its OpenAPI document, for example from CI | | POST | `/api/v1/docs/api-specs/{specID}/publish` | `docs:page:publish` | Publish the API reference | | POST | `/api/v1/docs/api-specs/{specID}/unpublish` | `docs:page:publish` | Unpublish the API reference | ### Files and redirects | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/docs/sites/{siteID}/assets` | `docs:site:read` | List uploaded files | | POST | `/api/v1/docs/sites/{siteID}/assets` | `docs:page:write` | Upload a file | | DELETE | `/api/v1/docs/assets/{assetID}` | `docs:page:write` | Delete a file | | GET | `/api/v1/docs/sites/{siteID}/redirects` | `docs:site:read` | List redirects | | POST | `/api/v1/docs/sites/{siteID}/redirects` | `docs:site:write` | Create a redirect | | PATCH | `/api/v1/docs/redirects/{redirectID}` | `docs:site:write` | Change a redirect | | DELETE | `/api/v1/docs/redirects/{redirectID}` | `docs:site:write` | Delete a redirect | ### Import and GitHub | Method | Path | Scope | What it does | | --- | --- | --- | --- | | POST | `/api/v1/docs/imports` | `docs:page:write` | Upload content from another tool and get a preview | | GET | `/api/v1/docs/imports/{importID}` | `docs:site:read` | Read an import and its preview | | POST | `/api/v1/docs/imports/{importID}/commit` | `docs:page:write` | Import it into a site | | DELETE | `/api/v1/docs/imports/{importID}` | `docs:page:write` | Discard an import | | GET | `/api/v1/docs/git/config` | `docs:site:read` | Whether GitHub sync is available | | GET | `/api/v1/docs/sites/{siteID}/git` | `docs:site:read` | The site's GitHub connection | | GET | `/api/v1/docs/sites/{siteID}/git/repos` | `docs:site:write` | Repositories the connection can use | | GET | `/api/v1/docs/sites/{siteID}/git/branches` | `docs:site:write` | Branches of a repository | | PUT | `/api/v1/docs/sites/{siteID}/git` | `docs:site:write` | Choose the repository, branch, folder and format | | DELETE | `/api/v1/docs/sites/{siteID}/git` | `docs:site:write` | Disconnect the site from GitHub | | POST | `/api/v1/docs/sites/{siteID}/git/sync` | `docs:page:write` | Sync from the repository now | Connecting a site to GitHub in the first place is done by a person in the console. See [GitHub sync](https://docs.evohub.io/github-sync.md) and [Import and export](https://docs.evohub.io/import-and-export.md). ### Domain and access | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/docs/sites/{siteID}/domain` | `docs:site:read` | The custom domain and its verification state | | PUT | `/api/v1/docs/sites/{siteID}/domain` | `docs:site:write` | Set the custom domain | | DELETE | `/api/v1/docs/sites/{siteID}/domain` | `docs:site:write` | Remove the custom domain | | GET | `/api/v1/docs/sites/{siteID}/access` | `docs:site:read` | Who may read the published site | | PUT | `/api/v1/docs/sites/{siteID}/access` | `docs:site:write` | Change who may read it | | PUT | `/api/v1/docs/sites/{siteID}/access/password` | `docs:site:write` | Set the site's password | | POST | `/api/v1/docs/sites/{siteID}/access/jwt/verify` | `docs:site:write` | Check a reader token against the site's settings | | POST | `/api/v1/docs/sites/{siteID}/access/sign-out` | `docs:site:write` | Sign every reader out | | GET | `/api/v1/docs/sites/{siteID}/access/read-tokens` | `docs:site:write` | List read tokens | | DELETE | `/api/v1/docs/sites/{siteID}/access/read-tokens/{tokenID}` | `docs:site:write` | Revoke a read token | See [Custom domain](https://docs.evohub.io/docs-custom-domain.md) and [Private docs](https://docs.evohub.io/private-docs.md). ### Analytics, feedback and link health | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/docs/analytics` | `docs:site:read` | Analytics for every site the key can see | | GET | `/api/v1/docs/sites/{siteID}/analytics` | `docs:site:read` | A site's page views and readers | | GET | `/api/v1/docs/sites/{siteID}/analytics/search` | `docs:site:read` | What readers searched for | | GET | `/api/v1/docs/sites/{siteID}/analytics/ai` | `docs:site:read` | Visits from AI assistants and crawlers | | GET | `/api/v1/docs/sites/{siteID}/analytics/gaps` | `docs:site:read` | Searches that found nothing | | GET | `/api/v1/docs/sites/{siteID}/analytics/export.csv` | `docs:site:read` | Download analytics as CSV | | GET | `/api/v1/docs/sites/{siteID}/feedback` | `docs:site:read` | List readers' feedback | | PATCH | `/api/v1/docs/feedback/{feedbackID}` | `docs:page:write` | Resolve or reopen feedback | | DELETE | `/api/v1/docs/feedback/{feedbackID}` | `docs:page:write` | Delete feedback | | GET | `/api/v1/docs/sites/{siteID}/health` | `docs:site:read` | The live site's link report | | POST | `/api/v1/docs/sites/{siteID}/health/check` | `docs:page:write` | Check the links again now | ### Activity log | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/docs/activity` | `docs:site:read` | What happened across every site the key can see | | GET | `/api/v1/docs/sites/{siteID}/activity` | `docs:site:read` | What happened on one site | ## Changelog ### Sites | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/changelog/sites` | `changelog:site:read` | List the changelogs the key can see | | GET | `/api/v1/changelog/sites/overview` | `changelog:site:read` | The list's figures for every changelog at once | | POST | `/api/v1/changelog/sites` | `changelog:site:write` | Create a changelog | | GET | `/api/v1/changelog/sites/{siteID}` | `changelog:site:read` | Read a changelog and its settings | | PATCH | `/api/v1/changelog/sites/{siteID}` | `changelog:site:write` | Change its settings; only the fields you send change | | DELETE | `/api/v1/changelog/sites/{siteID}` | `changelog:site:delete` | Delete a changelog with every entry on it | | POST | `/api/v1/changelog/sites/{siteID}/publish` | `changelog:site:write` | Put the changelog online or take it offline: `{"published": true}` | | POST | `/api/v1/changelog/sites/{siteID}/preview` | `changelog:site:read` | Create a private preview link of the live changelog | | POST | `/api/v1/changelog/sites/{siteID}/logo` | `changelog:site:write` | Upload the logo | | DELETE | `/api/v1/changelog/sites/{siteID}/logo` | `changelog:site:write` | Remove the logo | | POST | `/api/v1/changelog/sites/{siteID}/logo/dark` | `changelog:site:write` | Upload the dark-mode logo | | DELETE | `/api/v1/changelog/sites/{siteID}/logo/dark` | `changelog:site:write` | Remove the dark-mode logo | ### Categories | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/changelog/sites/{siteID}/categories` | `changelog:site:read` | List categories | | POST | `/api/v1/changelog/sites/{siteID}/categories` | `changelog:site:write` | Create a category | | PUT | `/api/v1/changelog/sites/{siteID}/categories/order` | `changelog:site:write` | Reorder categories | | PATCH | `/api/v1/changelog/categories/{categoryID}` | `changelog:site:write` | Change a category | | DELETE | `/api/v1/changelog/categories/{categoryID}` | `changelog:site:write` | Delete a category | ### Entries | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/changelog/sites/{siteID}/entries` | `changelog:site:read` | List entries; filter with `status`, `category`, `tag` and `q` | | POST | `/api/v1/changelog/sites/{siteID}/entries` | `changelog:entry:write` | Create an entry; it starts as a draft | | GET | `/api/v1/changelog/entries/{entryID}` | `changelog:site:read` | Read an entry with its review state | | PATCH | `/api/v1/changelog/entries/{entryID}` | `changelog:entry:write` | Edit an entry's draft | | DELETE | `/api/v1/changelog/entries/{entryID}` | `changelog:entry:write` | Delete an entry; a published one also needs `changelog:entry:publish` | ### Publishing | Method | Path | Scope | What it does | | --- | --- | --- | --- | | POST | `/api/v1/changelog/entries/{entryID}/submit` | `changelog:entry:write` | Submit the draft for review | | POST | `/api/v1/changelog/entries/{entryID}/publish` | `changelog:entry:publish` | Publish the draft now | | POST | `/api/v1/changelog/entries/{entryID}/schedule` | `changelog:entry:publish` | Publish at a later time: `{"publish_at": "…"}` | | POST | `/api/v1/changelog/entries/{entryID}/unschedule` | `changelog:entry:publish` | Cancel a scheduled publish | | POST | `/api/v1/changelog/entries/{entryID}/unpublish` | `changelog:entry:publish` | Take the entry off the changelog | | POST | `/api/v1/changelog/entries/{entryID}/git/resolve` | `changelog:entry:publish` | Settle a conflict between the entry and its GitHub file | On a changelog that requires review, publishing and scheduling need the approvals first and otherwise answer **409** `REVIEW_REQUIRED`. A key can submit an entry; a person approves it. See [Writing entries](https://docs.evohub.io/writing-entries.md). ### Revisions and images | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/changelog/entries/{entryID}/revisions` | `changelog:site:read` | List an entry's revisions | | GET | `/api/v1/changelog/entries/{entryID}/revisions/{revisionID}` | `changelog:site:read` | Read one revision | | POST | `/api/v1/changelog/entries/{entryID}/revisions/{revisionID}/restore` | `changelog:entry:write` | Put a revision back into the draft | | GET | `/api/v1/changelog/sites/{siteID}/assets` | `changelog:site:read` | List uploaded images | | POST | `/api/v1/changelog/sites/{siteID}/assets` | `changelog:entry:write` | Upload an image | | DELETE | `/api/v1/changelog/assets/{assetID}` | `changelog:entry:write` | Delete an image | ### Subscribers | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/changelog/sites/{siteID}/email` | `changelog:site:read` | How email goes out to subscribers | | GET | `/api/v1/changelog/sites/{siteID}/subscribers` | `changelog:site:read` | List subscribers | | GET | `/api/v1/changelog/sites/{siteID}/subscribers/export.csv` | `changelog:site:write` | Download subscribers as CSV | | DELETE | `/api/v1/changelog/subscribers/{subscriberID}` | `changelog:site:write` | Remove a subscriber | ### Slack and webhooks | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/changelog/chat-channels` | `changelog:site:write` | Chat app channels an integration can post to | | GET | `/api/v1/changelog/sites/{siteID}/webhooks` | `changelog:site:write` | List integrations | | POST | `/api/v1/changelog/sites/{siteID}/webhooks` | `changelog:site:write` | Create an integration | | PATCH | `/api/v1/changelog/webhooks/{webhookID}` | `changelog:site:write` | Change an integration | | DELETE | `/api/v1/changelog/webhooks/{webhookID}` | `changelog:site:write` | Delete an integration | | POST | `/api/v1/changelog/webhooks/{webhookID}/test` | `changelog:site:write` | Send a test delivery | | POST | `/api/v1/changelog/webhooks/{webhookID}/rotate-secret` | `changelog:site:write` | Replace the signing secret | | GET | `/api/v1/changelog/webhooks/{webhookID}/deliveries` | `changelog:site:write` | Recent deliveries and their results | See [Reaching readers](https://docs.evohub.io/reaching-readers.md). ### GitHub | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/changelog/git/config` | `changelog:site:read` | Whether GitHub sync is available | | GET | `/api/v1/changelog/sites/{siteID}/git` | `changelog:site:read` | The changelog's GitHub connection | | GET | `/api/v1/changelog/sites/{siteID}/git/repos` | `changelog:site:write` | Repositories the connection can use | | GET | `/api/v1/changelog/sites/{siteID}/git/branches` | `changelog:site:write` | Branches of a repository | | PUT | `/api/v1/changelog/sites/{siteID}/git` | `changelog:site:write` | Choose the repository and branch | | DELETE | `/api/v1/changelog/sites/{siteID}/git` | `changelog:site:write` | Disconnect from GitHub | | POST | `/api/v1/changelog/sites/{siteID}/git/sync` | `changelog:entry:write` | Sync from the repository now | | POST | `/api/v1/changelog/sites/{siteID}/imports/github` | `changelog:entry:write` | Import GitHub releases as entries | Connecting a changelog to GitHub in the first place is done by a person in the console. See [GitHub](https://docs.evohub.io/changelog-github.md). ### Domain and access | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/changelog/sites/{siteID}/domain` | `changelog:site:read` | The custom domain and its verification state | | PUT | `/api/v1/changelog/sites/{siteID}/domain` | `changelog:site:write` | Set the custom domain | | DELETE | `/api/v1/changelog/sites/{siteID}/domain` | `changelog:site:write` | Remove the custom domain | | GET | `/api/v1/changelog/sites/{siteID}/access` | `changelog:site:read` | Who may read the changelog | | PUT | `/api/v1/changelog/sites/{siteID}/access` | `changelog:site:write` | Change who may read it | | PUT | `/api/v1/changelog/sites/{siteID}/access/password` | `changelog:site:write` | Set the changelog's password | | POST | `/api/v1/changelog/sites/{siteID}/access/sign-out` | `changelog:site:write` | Sign every reader out | ### Analytics and feedback | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/changelog/analytics` | `changelog:site:read` | Analytics for every changelog the key can see | | GET | `/api/v1/changelog/sites/{siteID}/analytics` | `changelog:site:read` | A changelog's views and readers | | GET | `/api/v1/changelog/sites/{siteID}/analytics/export.csv` | `changelog:site:read` | Download analytics as CSV | | GET | `/api/v1/changelog/sites/{siteID}/feedback` | `changelog:site:read` | List readers' feedback | | PATCH | `/api/v1/changelog/feedback/{feedbackID}` | `changelog:site:write` | Resolve or reopen feedback | | DELETE | `/api/v1/changelog/feedback/{feedbackID}` | `changelog:site:write` | Delete feedback | ### Activity log | Method | Path | Scope | What it does | | --- | --- | --- | --- | | GET | `/api/v1/changelog/activity` | `changelog:site:read` | What happened across every changelog the key can see | | GET | `/api/v1/changelog/sites/{siteID}/activity` | `changelog:site:read` | What happened on one changelog | ## Create and publish a changelog entry A release pipeline can write the entry and publish it in two calls. The key needs `changelog:entry:write` and `changelog:entry:publish`. ```bash # 1. Create the entry (a draft) curl https://evohub.io/api/v1/changelog/sites/$SITE_ID/entries \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Faster exports", "summary": "CSV exports now finish in seconds.", "body_md": "Exports of large projects used to take minutes. They now stream as they are built.", "version": "2.4.0", "category_ids": ["'"$CATEGORY_ID"'"], "tags": ["exports"] }' # 2. Publish it, and tell subscribers and integrations curl -X POST https://evohub.io/api/v1/changelog/entries/$ENTRY_ID/publish \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"notify": true}' ``` `title` is required; a blank `slug` is made from the title. The answer to the first call is the entry, with its `id`. `notify` is optional; leave it out to keep the entry's setting. To publish later instead, call `/schedule` with `{"publish_at": "2026-12-01T09:00:00Z"}`. Entry fields also include `cover_image_url`, `pinned`, `author_name` and `date`. ## Update and publish a docs page ```bash # 1. Edit the draft; only the fields you send change curl -X PATCH https://evohub.io/api/v1/docs/pages/$PAGE_ID \ -H "Authorization: Bearer $EVOHUB_API_KEY" \ -H "Content-Type: application/json" \ -H 'If-Match: "7"' \ -d '{"title": "Install the CLI", "body_md": "Run `brew install acme` and sign in."}' # 2. Publish the draft curl -X POST https://evohub.io/api/v1/docs/pages/$PAGE_ID/publish \ -H "Authorization: Bearer $EVOHUB_API_KEY" ``` Every save answers with an `ETag` holding the page's version. Send it back as `If-Match` (or as `version` in the body) and a save made over someone else's newer edit is refused with **409** `EDIT_CONFLICT`, carrying the page as it now stands; leave both out to overwrite. A page's section is changed with `PUT /api/v1/docs/sites/{siteID}/order`, not on the page. To create a page, `POST /api/v1/docs/sites/{siteID}/pages` with `title`, and optionally `slug`, `body_md` and `section_id`; `?version=` picks the docs version, and the default version is used without it. On a site that requires review, step 2 answers **409** `REVIEW_REQUIRED`: open a change request for the page with `POST /api/v1/docs/sites/{siteID}/change-requests` and a `title`, and a person approves and merges it. ## What is not part of the API - **Reserved for a person.** Approving, requesting changes on, merging and reverting docs change requests and approving changelog entries; connecting a docs site or a changelog to GitHub; and issuing private docs credentials (a new reader-token signing secret, a test token, a read token) are done by a person in the console. A key gets **403** `PERSON_REQUIRED`. See [What a key can never do](https://docs.evohub.io/api-keys-and-scopes.md#what-a-key-can-never-do). - **Public-site routes.** The addresses under `/api/v1/docs/public/` and `/api/v1/changelog/public/` serve your published sites to their visitors. They are not part of the API, may change without notice, and do not take an API key. - **Console-only helpers** such as the console home, notifications and who-is-editing indicators are left out here. ## Related - [API overview](https://docs.evohub.io/api-overview.md) - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) - [Errors](https://docs.evohub.io/errors.md) - [Docs overview](https://docs.evohub.io/docs-overview.md) - [Review and change requests](https://docs.evohub.io/review-and-change-requests.md) - [Changelog overview](https://docs.evohub.io/changelog-overview.md) --- Source: https://docs.evohub.io/security.md # Security at EvoHub This page describes the measures that protect your EvoHub account and your organization's data, and the settings you control. It only lists what EvoHub actually does today. ## Encryption in transit All traffic to EvoHub, including the console, the API and the mobile apps, is encrypted in transit with TLS 1.2 or higher. ## Passwords - Passwords are hashed with bcrypt, a salted one-way hash, before they are stored. EvoHub never stores or logs a password in plain text, and nobody at EvoHub can read yours. - A password must be at least 8 characters. Administrators can raise the minimum for their organization (see [Login policies](#login-policies)). - You can sign in with Google or GitHub instead of a password, and connect or disconnect those accounts under **My Account → Security → Connected accounts**. ## Sign-in protection - **Account lock.** After 5 wrong passwords, the account is locked for 15 minutes, even for the right password. This makes guessing passwords slow and expensive. - **Bot protection.** The sign-in, sign-up, password-reset and verification-email forms are protected by Cloudflare Turnstile, which tells people apart from automated abuse. - **Two-factor authentication.** Add a time-based one-time code from an authenticator app to every sign-in. See [Two-factor authentication](https://docs.evohub.io/two-factor-authentication.md). - **Short-lived links.** Password reset links are valid for 15 minutes and email verification links for 24 hours. ## Sessions - After you sign in, the console holds a short-lived, signed access token that is renewed in the background while you use EvoHub. Sessions are stored on EvoHub's servers and can be ended at any time. - A session ends after a period of inactivity. The default is 7 days; administrators can change it (see [Login policies](#login-policies)). - **My Account → Active Sessions** lists every device signed in to your account, with its IP address, when it was last seen and when it signed in. Select **Revoke** to sign a device out. See [Two-factor authentication](https://docs.evohub.io/two-factor-authentication.md#review-your-sessions). - Signing out ends the session. Deleting your account ends all of them. ## Login policies Administrators can set security requirements for everyone in an organization under **Organization → Login Policies**: | Setting | What it does | | --- | --- | | **Require MFA** | Every member must set up two-factor authentication before they can enter the organization. | | **Session Timeout** | Hours of inactivity after which members are signed out. Default 168 (7 days). | | **Minimum Password Length** | Passwords shorter than this are rejected when members set or change a password. | Select **Save Policies** to apply them. ## Tenant isolation Every organization's data is kept apart from every other organization's. - Every request is tied to exactly one organization by the signed token or API key it carries. EvoHub's servers decide which organization a request belongs to; a client cannot choose it. - Every read and write of organization data is filtered by that organization. An ID that belongs to another organization simply is not found. - Inside an organization, work can be scoped to a team. Something that belongs to a team you cannot see is reported as not found, so its existence is not revealed. ## Authorization on the server - Every action is checked against the caller's organization roles on EvoHub's servers. The console hides what you cannot do, but the API enforces it independently. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). - You can never grant a role, or give an API key, a permission you do not hold yourself. - A refused action returns 403. It never signs you out. ## API keys - An API key is shown once, when it is created. EvoHub stores only a SHA-256 hash of it, so a key cannot be recovered from EvoHub, even by EvoHub. - A key has exactly the scopes it was given, can expire, and can be revoked instantly. A key never counts as an administrator. - A personal key loses whatever its owner loses, and stops working when its owner leaves the organization or deletes their account. - Every key starts with `evohub_`, so secret scanners can recognize a leaked key. See [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md). ## Payment details Card details are entered into a form provided by Stripe, EvoHub's payment processor. Card numbers go directly to Stripe and never reach EvoHub's servers. ## Audit logs **Audit Logs** in EvoTrail records who did what in every product of the organization: sign-ins, creating and revoking API keys, changes to members, roles and settings, and the changes people make in On-Call, Uptime, status pages, retros, boards, docs and changelogs. Opening it needs `evotrail:audit:read`, which the default Owner, Admin and Member roles hold and Viewer does not, and each row also needs its product's audit permission. Changes are kept for 365 days, day-to-day activity for 90 days, and IP addresses and browser details for 90 days. See [Audit Logs](https://docs.evohub.io/evotrail-audit-logs.md). ## Report a security problem If you believe your account has been compromised, or you have found a security issue in EvoHub, email info@evosync.io right away. ## Related - [Two-factor authentication](https://docs.evohub.io/two-factor-authentication.md) - [Privacy and your data](https://docs.evohub.io/privacy-and-data.md) - [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md) - [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md) --- Source: https://docs.evohub.io/two-factor-authentication.md # Two-factor authentication Two-factor authentication (2FA, also called MFA) adds a second step to signing in: after your password, or after Google or GitHub, EvoHub asks for a 6-digit code from an authenticator app on your phone. This page explains how to turn it on, how an organization can require it, and how to manage your signed-in devices. ## What you need An authenticator app that supports time-based one-time passwords (TOTP), such as Google Authenticator, Authy, 1Password or Microsoft Authenticator. EvoHub does not send codes by SMS or email. ## Turn on two-factor authentication :::steps ### Open Authentication Open the avatar menu, choose **My Account**, then **Security**. Next to **Two-factor authentication**, select **Enable**. ### Show the QR code The QR code is hidden so that nobody nearby can scan it. Select **Show QR code** when you are ready. ### Scan it Scan the QR code with your authenticator app. If you cannot scan, type the key shown under **Can't scan? Enter this key manually** into the app instead. Then select **I've scanned it**. ### Confirm with a code Enter the 6-digit code your app now shows and select **Enable MFA**. ::: From now on, every sign-in to your account asks for a code. > [!WARNING] > EvoHub does not currently provide backup codes. Before you finish setup, keep a copy of the setup key somewhere safe, such as your password manager, or add the account to a second device. With the key you can restore the codes on a new phone. ## Sign in with a code 1. Sign in with your email and password, or with Google or GitHub. 2. On the **Two-factor authentication** screen, enter the current 6-digit code from your app. The code screen is valid for 5 minutes. If it expires, start the sign-in again. Codes change every 30 seconds; if one is rejected, wait for the next one and check that your phone's clock is set automatically. ## Turn off two-factor authentication Go to **My Account → Security**, select **Disable** next to **Two-factor authentication**, enter a current code from your authenticator app and select **Disable MFA**. A current code is required, so someone who only knows your password cannot turn it off. If your organization requires two-factor authentication, you will be asked to set it up again the next time you sign in. ## If you lose your device - If you saved the setup key, add it to the authenticator app on your new device and sign in as usual. Then turn 2FA off and on again to get a fresh key. - Turning 2FA off also needs a code, so being signed in elsewhere does not help on its own; restore the key first. - If you have neither the key nor a second device, contact support at info@evosync.io from the email address on your account. ## Require two-factor authentication for an organization Administrators can make 2FA mandatory for everyone in an organization. :::steps ### Open Login Policies Go to **Organization → Login Policies**. ### Turn on Require MFA Switch on **Require MFA** ("All members must enable two-factor authentication") and select **Save Policies**. ::: What happens next: - Members who already use 2FA notice nothing. - A member without 2FA is taken through setup the next time they sign in, whether with a password or with Google or GitHub, and enters the organization only after it is done. - Someone switching into the organization from another one is asked to set up 2FA first. ## Review your sessions **My Account → Active Sessions** lists every device signed in to your account: browser and operating system, IP address, when it was last seen and when it signed in. Your current device is marked **Current session**, and the mobile app is marked **MOBILE**. To sign a device out, select **Revoke** and confirm with **Revoke Session**. Do this for any device you do not recognize, and then change your password. Sessions also end on their own after a period of inactivity, 7 days by default. Administrators can change this under **Organization → Login Policies → Session Timeout**. ## Related - [Security at EvoHub](https://docs.evohub.io/security.md) - [Create your account](https://docs.evohub.io/create-your-account.md) - [Privacy and your data](https://docs.evohub.io/privacy-and-data.md) --- Source: https://docs.evohub.io/privacy-and-data.md # Privacy and your data This page explains the choices you have over your data in EvoHub: deleting your account or an organization, how long different kinds of data are kept, and how cookies and analytics work. The legal details are in the [Privacy Notice](https://evohub.io/privacy), which takes precedence if anything here differs. ## Who can see your data - Everything you create in EvoHub belongs to an organization. Other organizations can never see it. - Inside the organization, members see what their roles and teams allow. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). - EvoHub is operated by EvoSync Teknoloji A.Ş. (Istanbul, Türkiye), which is the data controller (veri sorumlusu) under the Turkish Personal Data Protection Law (KVKK). EvoHub does not sell personal data and does not use it to show you third-party advertising. ## Delete your account :::steps ### Open your profile Open the avatar menu, choose **My Account**, then **Profile**, and scroll to **Delete Account**. ### Confirm Select **Delete Account**, type your email address to confirm, and select **Delete my account**. ::: What happens: - **Immediately:** all your sessions are ended and your personal API keys stop working. - **For 30 days:** the account is scheduled for deletion but kept intact. Signing back in during this time cancels the deletion and restores the account as it was, except that personal API keys stay revoked; create new ones. - **After 30 days:** the account is permanently deleted, including your profile, notification preferences, push notification registrations, Google and GitHub connections and your memberships of organizations. Your personal organization is deleted with it if nobody else is in it. > [!NOTE] > You cannot delete your account while you are the only Owner of an organization that has other members, because that organization would be left without an owner. Delete that organization first, or contact info@evosync.io. Content you created inside an organization, such as incidents or board cards, belongs to that organization and stays with it. ## Delete an organization The Owner can delete an organization under **Organization → Organization settings → Danger Zone** by typing its name. Members lose access immediately, and the organization's data in every product is removed: On-Call, Uptime, status pages, retros, boards, docs sites and changelogs. This is permanent. Personal organizations cannot be deleted; they go when their owner's account is purged. See [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md#delete-an-organization). ## How long data is kept | Data | Kept for | | --- | --- | | Account data | While your account exists, and 30 days after you delete it | | Resolved alerts and their notification and timeline records | 1 year, then deleted automatically | | Incidents, postmortems and on-call configuration (schedules, rotations, escalation policies) | As long as the account exists | | Uptime check results | 90 days | | Board cards | Until you archive or delete them; archived cards are deleted 2 years after archiving | | Audit trail: changes | 365 days | | Audit trail: day-to-day activity, such as moving a card | 90 days | | IP addresses and browser details on audit events | 90 days | | Billing and usage records that make up your invoices | As long as tax and accounting law requires, also after an account or organization is closed | ## Where data is processed EvoHub is operated from servers in Türkiye. If you use EvoHub from elsewhere, your data is transferred to and processed there. The [Privacy Notice](https://evohub.io/privacy) describes the safeguards for international transfers. EvoHub uses service providers to run the platform: providers for voice, email and mobile push notifications, a payment processor, cloud infrastructure, Cloudflare for bot protection on sign-in and sign-up forms, and, only if you accept optional cookies on the website, Google for analytics and ad measurement. A current list of sub-processors is available on request from info@evosync.io. ## Cookies on evohub.io - Cookies that are essential for signing in and running the console are always set. - Optional cookies are set only if you choose **Accept all** in the cookie banner: Google Analytics, to understand how the site is used, and Google Ads measurement, to learn which of EvoHub's own ads led to a visit or sign-up. - If you choose **Essential only**, neither is set. Choosing it later removes the Google cookies already set. - Change your choice at any time with **Cookie settings** at the bottom of every page. The sign-in, sign-up, password-reset and verification-email forms use Cloudflare Turnstile for bot protection regardless of your cookie choice, because it is needed to keep accounts secure. It processes technical signals such as your IP address and browser, does not read what you type, and is not used for advertising. ## Analytics on docs sites and changelogs Docs sites, changelogs and status pages published with EvoHub measure their readership without cookies: - No cookie is set and no IP address is stored. A reader's IP address and browser details are turned into a one-way code that cannot be traced back to them. - Visitors are counted per day, not tracked across days. - Readers whose browser sends Do Not Track or Global Privacy Control are not counted at all. Because nothing identifies a person, these analytics need no cookie banner. If a site's owner adds their own analytics tool, that tool's cookies and consent are the owner's responsibility. See [API references and AI](https://docs.evohub.io/api-references-and-ai.md) and [Changelog settings](https://docs.evohub.io/changelog-settings.md). ## Emails from EvoHub EvoHub sends transactional emails you need, such as email verification, password resets, invitations and alert notifications. Product announcements can be turned off with the unsubscribe link in any such email. ## Your rights Depending on where you live, you can ask for access to, correction of, deletion of, or a portable copy of your personal data, and you can object to or restrict some processing. To exercise any of these rights, email info@evosync.io. EvoHub usually responds within one business day, and always within 30 days. ## Related - [Security at EvoHub](https://docs.evohub.io/security.md) - [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md) - [Privacy Notice](https://evohub.io/privacy) --- Source: https://docs.evohub.io/faq.md # FAQ Short answers, each with a link to the page that explains more. ## Pricing and billing :::details{title="Is EvoHub priced per user?"} No. EvoHub charges for usage, never per seat, so inviting your whole team costs nothing. Usage is measured in EvoHub tokens (EHU), and every organization gets 200 EHU free each month. See [How billing works](https://docs.evohub.io/how-billing-works.md) and the [pricing page](https://evohub.io/pricing). ::: :::details{title="Do I need a credit card to start?"} No. Signing up does not ask for one, and a month spent inside the free allowance costs nothing. You only pay for usage above the allowance, and only after you add a card or buy credit. ::: :::details{title="What happens when the free allowance runs out and no card is on file?"} Alerts are still received and recorded, but notifications are held back until a card or credit is added, and each alert's timeline says so. See [How billing works](https://docs.evohub.io/how-billing-works.md). ::: :::details{title="What costs nothing?"} Push notifications and outbound webhooks are free, and so are Retro, Board and EvoTrail. A heartbeat monitor costs one uptime check only when it goes down or comes back, never per ping. Repeats of an alert that is still open are not counted either. See [How billing works](https://docs.evohub.io/how-billing-works.md). ::: ## On-Call :::details{title="Does EvoHub send SMS?"} Not yet. SMS is coming soon; until it ships it has no price. EvoHub pages people by phone call, mobile push and email. See [Notifications](https://docs.evohub.io/notifications.md). ::: :::details{title="Which monitoring tools can send alerts to EvoHub?"} 44 integrations, from Prometheus, Grafana and Datadog to Zabbix, PRTG and email, plus a generic webhook and an Events API v2 compatible API for anything else. See [Integrations overview](https://docs.evohub.io/integrations-overview.md). ::: :::details{title="Can I move from Opsgenie or PagerDuty?"} Yes. The built-in importers bring over schedules, escalation policies and integrations. See [Migrate from Opsgenie](https://docs.evohub.io/migrate-from-opsgenie.md) and [Migrate from PagerDuty](https://docs.evohub.io/migrate-from-pagerduty.md). ::: :::details{title="Does On-Call have services?"} No. An alert is routed by the integration it arrived through, and that integration's escalation policy decides who is paged. See [On-Call overview](https://docs.evohub.io/on-call-overview.md). ::: :::details{title="What can I do in the mobile app?"} The iOS and Android app is an on-call companion: get paged, acknowledge, take over, redirect and resolve alerts, cover a shift and see who is on call. Schedules, escalation policies and integrations are set up in the web console. See [Mobile app](https://docs.evohub.io/mobile-app.md). ::: ## Status pages, docs and changelogs :::details{title="Can I publish a status page without my own domain?"} No. Status pages, docs sites and changelogs are published on a subdomain you own, such as `status.example.com`; EvoHub does not give them an address of its own, and apex domains such as `example.com` are not supported. See [Status page custom domain](https://docs.evohub.io/status-page-custom-domain.md). ::: :::details{title="How is the uptime percentage on a status page calculated?"} Day by day, from the same rule that colours the bars: an incident costs the share of the day it lasted, maintenance does not count as downtime, and a monitored component's days follow its monitor. See [Uptime bars and percentage](https://docs.evohub.io/uptime-history.md). ::: :::details{title="Can I choose where uptime checks run from?"} Not today. Each check's location is shown in the monitor's **Recent Checks** table. See [Uptime overview](https://docs.evohub.io/uptime-overview.md). ::: ## Accounts and security :::details{title="Does EvoHub support SSO, SAML or SCIM?"} No. You sign in with email and password, or with Google or GitHub. Single sign-on through SAML or OIDC with your own identity provider, and SCIM provisioning, are not available. Administrators can require two-factor authentication and set session and password rules in **Organization → Login Policies**. See [Security](https://docs.evohub.io/security.md). ::: :::details{title="Which two-factor methods are there?"} An authenticator app with 6-digit codes (TOTP), such as Google Authenticator, 1Password or Authy. Codes are not sent by SMS or email. See [Two-factor authentication](https://docs.evohub.io/two-factor-authentication.md). ::: :::details{title="Can I belong to several organizations?"} Yes, with a different role in each. Switch between them with the organization switcher in the top bar. One account can own up to 5 organizations. See [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md). ::: :::details{title="Where is my data processed, and how long is it kept?"} EvoHub is operated from servers in Türkiye. Retention differs by kind of data, for example 1 year for resolved alerts and 90 days for uptime check results. See [Privacy and your data](https://docs.evohub.io/privacy-and-data.md) and [Limits](https://docs.evohub.io/limits.md#how-long-data-is-kept). ::: ## Help :::details{title="How do I get help?"} Open a ticket from **Help & Support** in the avatar menu, or use the bug button in the bottom-right corner of the console. The EvoHub team replies in the ticket, and you get an email when they do. You can also write to **info@evosync.io**. See [Get help](https://docs.evohub.io/get-help.md). ::: :::details{title="How do I replay a product tour?"} Go to **My Account → Preferences** (avatar menu, **My Account**). See [Setup, tours and tips](https://docs.evohub.io/setup-tours-and-tips.md). ::: ## Related - [Troubleshooting](https://docs.evohub.io/troubleshooting.md) - [Limits](https://docs.evohub.io/limits.md) - [Quickstart](https://docs.evohub.io/quickstart.md) --- Source: https://docs.evohub.io/troubleshooting.md # Troubleshooting This page collects the problems people meet most often, in the order to check them. Most answers start in the same place: an alert's **timeline** in **On-Call → Alerts** records every notification EvoHub sent, held back or skipped, and why. ## I didn't get a notification Open the alert in **On-Call → Alerts** and read its timeline. It says who was notified, on which channel, and why a notification was not sent. The usual reasons: - **Nobody was targeted.** The integration has no escalation policy (the alert is recorded, but nobody is paged), or the step targeted a schedule with nobody on call at that moment. See [Escalation policies](https://docs.evohub.io/escalation-policies.md) and [Schedules and rotations](https://docs.evohub.io/schedules-and-rotations.md). - **The step has not run yet.** A step runs after its **Notify after** delay. With a first step of 5 or 15 minutes, an alert that resolves sooner pages nobody. - **It was a repeat.** An alert with the same fingerprint as one that is still open is recorded as **Retriggered** on that alert, and nobody is paged again. See [Deduplication](https://docs.evohub.io/integrations-overview.md#deduplication). - **A maintenance window was running.** On-Call pages nobody during maintenance; the timeline says the notification was suppressed by maintenance. See [Maintenance windows](https://docs.evohub.io/alerts-and-incidents.md#maintenance-windows). - **Your notification schedule held it back.** A rule in **My Account → Notifications → Notification schedule** did not allow that channel at that time. See [Notification schedule](https://docs.evohub.io/notifications.md#notification-schedule). - **The channel is not set up.** - **Phone Call** needs a verified phone number. - **Mobile Push** needs you signed in to the EvoHub app on a device, with notifications allowed in the phone's settings. **Profile → Push Notification Devices** in the app lists the devices that receive push. See [Mobile app](https://docs.evohub.io/mobile-app.md). - **Email** goes to your account email; check your spam folder. - **You are not allowed to respond.** People in rotations, escalation steps and overrides must hold the permission to respond to alerts. See [Roles and permissions](https://docs.evohub.io/roles-and-permissions.md). - **The free allowance is used up.** When an organization has used its free monthly allowance and has no card or credit on file, alerts are still recorded but notifications are held back, and the timeline says so. See [How billing works](https://docs.evohub.io/how-billing-works.md). To test the whole path, create an alert with **On-Call → Alerts → New Alert** and pick an escalation policy whose first step targets you. See [Quickstart](https://docs.evohub.io/quickstart.md). > [!NOTE] > EvoHub does not send SMS yet; SMS is coming soon. Use push or phone calls for urgent paging. ## An alert did not open Start with what the monitoring tool says about its last delivery (most tools keep a delivery or action log), then check the integration in **On-Call → Integrations**: **Last Event** shows when EvoHub last heard from it. | What you see | What it means | | --- | --- | | The tool shows `401` with `INVALID_KEY` | The key in the URL is wrong, or the integration is disabled or deleted. Copy the **Webhook URL** again; **Enable** the integration if it is disabled. | | `403` with `INVALID_SIGNATURE` or `SIGNATURE_REQUIRED` | The integration has a signing secret, and the delivery's signature is missing or wrong. See [Signing secrets](https://docs.evohub.io/integrations-overview.md#signing-secrets). | | `400` with `INVALID_BODY` | The body is not JSON EvoHub can read for that tool. Check the body template against the integration's page. | | `413` | The body is larger than 1 MiB. | | `503` with `INGEST_UNAVAILABLE` | A brief problem on EvoHub's side. The tool should retry; most do on their own. | | `200`, but no new alert | The delivery was read but opened nothing: it was a recovery, the tool's own test notification (answered with `"test": true`), or a repeat of an alert that is still open (see **Retriggered** on that alert). | | An alert titled *<tool> alert (payload could not be read)* | The tool's body template produced broken JSON. Fix the template, then resolve that alert by hand. See [Bodies that cannot be read](https://docs.evohub.io/integrations-overview.md#bodies-that-cannot-be-read). | | **Last Event** never changes | Nothing reaches EvoHub. Check the URL in the tool, and that the tool's alert rule actually uses the webhook, contact point or media type you created. | If alerts open but never resolve, the tool is not sending its recovery notification, or sends it with different identifiers than the alert it should close. Each integration page says how to turn recoveries on. ### Per-integration troubleshooting These pages have a troubleshooting section for their tool: [Prometheus](https://docs.evohub.io/prometheus-alertmanager.md#troubleshooting), [Grafana](https://docs.evohub.io/grafana.md#troubleshooting), [Datadog](https://docs.evohub.io/datadog.md#troubleshooting), [Zabbix](https://docs.evohub.io/zabbix.md#troubleshooting), [PRTG](https://docs.evohub.io/prtg.md#troubleshooting), [Uptime Kuma](https://docs.evohub.io/uptime-kuma.md#troubleshooting), [New Relic](https://docs.evohub.io/new-relic.md#troubleshooting), [Azure Monitor](https://docs.evohub.io/azure-monitor.md#troubleshooting), [Dynatrace](https://docs.evohub.io/dynatrace.md#troubleshooting), [Sentry](https://docs.evohub.io/sentry.md#troubleshooting), [Google Cloud Monitoring](https://docs.evohub.io/google-cloud-monitoring.md#troubleshooting), [UptimeRobot](https://docs.evohub.io/uptimerobot.md#troubleshooting), [Pingdom](https://docs.evohub.io/pingdom.md#troubleshooting), [Site24x7](https://docs.evohub.io/site24x7.md#troubleshooting), [StatusCake](https://docs.evohub.io/statuscake.md#troubleshooting), [Nagios](https://docs.evohub.io/nagios.md#troubleshooting), [Cortex XDR](https://docs.evohub.io/cortex-xdr.md#troubleshooting), [OpenSearch](https://docs.evohub.io/opensearch.md#troubleshooting) and [KOI](https://docs.evohub.io/koi.md#troubleshooting). For every other tool, its page in [Integrations](https://docs.evohub.io/integrations-overview.md#supported-integrations) explains what EvoHub reads and when an alert resolves, which is usually where the answer is. ### Uptime monitors A monitor that goes down opens an On-Call alert only if it has an **Escalation Policy** under **Alerting**. While a silence window covers a monitor, its alerts are muted; it keeps checking. See [Uptime alerts and silence windows](https://docs.evohub.io/uptime-alerts-and-silence-windows.md). ## A status page is not updating - **Incidents appear only when published.** An incident in On-Call reaches a status page only after you choose **Publish to status page** on it. From then on, its timeline notes, status changes and resolve follow it to the page. See [Incidents and maintenance](https://docs.evohub.io/status-incidents-and-maintenance.md). - **Give it half a minute.** Changes in the console and incidents published from On-Call reach visitors within about 30 seconds, and an open page refreshes itself. - **A component synced to a monitor follows the monitor.** Its status cannot be set by hand. See [Components and groups](https://docs.evohub.io/components-and-groups.md). - **Impact None changes no colour.** An incident published with impact **None** shows on the page, but leaves the components' colour and the uptime percentage as they were. See [Uptime bars and percentage](https://docs.evohub.io/uptime-history.md). - **The page says "This status page is not configured".** The page was taken offline. Choose **Publish status page** on the banner in **Settings**. - **Nothing at all on your domain.** The domain is not verified yet; see the next section. ## A custom domain is not verifying Status pages, docs sites and changelogs connect a domain the same way: two CNAME records, added in order. 1. **Check the hostname.** It must be a subdomain such as `status.example.com`; apex domains such as `example.com` are not supported. A hostname can serve only one status page, docs site or changelog. 2. **Check the validation record.** The `_acme-challenge` CNAME must match the console exactly, name and value. Some DNS providers add your domain to the name on their own, so `_acme-challenge.status.example.com` becomes `_acme-challenge.status.example.com.example.com`; in that case enter only `_acme-challenge.status`. 3. **Then the routing record.** The subdomain's CNAME must point to `cname.evohub-dns.com`. 4. **Cloudflare.** If the domain's DNS is on Cloudflare with the proxy on, set SSL/TLS to **Full**, not **Flexible**. 5. **Wait, then Re-check.** DNS changes can take a while to spread, depending on the records' TTL. The console keeps checking while the domain is **Pending DNS** or **Validating**; **Re-check** checks now. 6. **Action needed** means the domain was blocked or its certificate expired. Check that both records are still in place exactly as shown and choose **Re-check**. If it stays that way, remove the domain and connect it again. Keep both records for as long as you use the domain: the certificate renews through the `_acme-challenge` record. See [Status page custom domain](https://docs.evohub.io/status-page-custom-domain.md), [Docs custom domain](https://docs.evohub.io/docs-custom-domain.md) and [Changelog settings](https://docs.evohub.io/changelog-settings.md#custom-domain). ## Still stuck? Open a ticket from **Help & Support** in the console and include what you checked. For a failed API call, add the `X-Request-ID` from the response. See [Get help](https://docs.evohub.io/get-help.md). ## Related - [FAQ](https://docs.evohub.io/faq.md) - [Limits](https://docs.evohub.io/limits.md) - [Errors](https://docs.evohub.io/errors.md) --- Source: https://docs.evohub.io/limits.md # Limits This page lists the limits you can run into in EvoHub. Each product page explains its own in context; the link in each row goes there. ## Rate limits | What | Limit | | --- | --- | | API requests | Counted per API key (or, without a key, per IP address) over a 10-second window: about 100 requests per second. Over it, the answer is **429** `RATE_LIMITED` with `Retry-After: 10`. See [Rate limits](https://docs.evohub.io/api-overview.md#rate-limits). | | Alert ingest and heartbeat pings | Not rate limited, so a burst of alerts during an outage is never dropped for being too many. | | Failed sign-ins | After 5 wrong passwords, the account is locked for 15 minutes. See [Sign in](https://docs.evohub.io/create-your-account.md#sign-in). | | Changelog GitHub release imports | 10 imports an hour per changelog. See [Import GitHub releases](https://docs.evohub.io/writing-entries.md#import-github-releases). | ## Sizes | What | Limit | | --- | --- | | Alert ingest body | 1 MiB; larger is refused with **413**. See [How ingest works](https://docs.evohub.io/integrations-overview.md#how-ingest-works). | | Uptime check response | The first 1 MB of the body is read. See [HTTP and keyword options](https://docs.evohub.io/uptime-overview.md#http-and-keyword-options). | | Status page, docs site and changelog logos | PNG, JPEG or WebP up to 2 MB. | | Docs site files | Images up to 5 MB (SVG and ICO 1 MB), PDFs up to 20 MB; 500 MB and 2,000 files per site. See [Files and images](https://docs.evohub.io/writing-pages.md#files-and-images). | | Docs import | A `.zip` of up to 50 MB. See [Import and export](https://docs.evohub.io/import-and-export.md). | | Changelog images | PNG, JPEG, WebP, GIF or SVG up to 5 MB each. See [Writing entries](https://docs.evohub.io/writing-entries.md). | | Voice template | 200 characters. See [Customize what the call says](https://docs.evohub.io/notifications.md#customize-what-the-call-says). | | Support ticket subject | 200 characters. | ## Per-product limits | Product | Limit | | --- | --- | | Organizations | One account can own up to 5 organizations. See [Organizations and teams](https://docs.evohub.io/organizations-and-teams.md). | | On-Call | An escalation step tries a target up to 10 times; a policy repeats up to 5 times. Up to 20 rules in your notification schedule. A maintenance window lasts at most 30 days. See [Escalation policies](https://docs.evohub.io/escalation-policies.md) and [Notifications](https://docs.evohub.io/notifications.md). | | Uptime | Checks every 15 seconds at the most often, with a timeout of up to 60 seconds; up to 10 assertions per monitor. See [Uptime overview](https://docs.evohub.io/uptime-overview.md). | | Status Pages | One custom domain per page; up to 5 header links; **A page per group** allows up to 3 groups. See [Status Pages](https://docs.evohub.io/status-pages-overview.md). | | Docs | Up to 20 versions and 10 extra languages per site, 200 snippets per version. See [Docs overview](https://docs.evohub.io/docs-overview.md). | | Changelog | Up to 30 categories per changelog, 5 categories and 10 tags per entry, 10 notification destinations, 5 header links, 20 allowed sites for the iframe. The widget shows the 10 newest entries. See [Writing entries](https://docs.evohub.io/writing-entries.md) and [Reaching readers](https://docs.evohub.io/reaching-readers.md). | | EvoTrail | A custom date range covers up to 366 days; an audit export carries up to 100,000 events. See [EvoTrail overview](https://docs.evohub.io/evotrail-overview.md). | ## How long data is kept | Data | Kept for | | --- | --- | | Resolved alerts, with their notification and timeline records | 1 year, then deleted automatically | | Incidents, postmortems and on-call configuration | As long as the account exists | | Uptime check results | 90 days (daily figures: 400 days) | | Board cards | Until you archive or delete them; archived cards are deleted 2 years after archiving | | Audit trail: changes | 365 days | | Audit trail: day-to-day activity, such as moving a card | 90 days | | IP addresses and browser details on audit events | 90 days | | Docs, changelog and status page visit figures | Each site's own setting: 30, 90 or 180 days, 1 year or 2 years | | Docs page feedback | 2 years | | Account data | While your account exists, and 30 days after you delete it | | Billing and usage records that make up invoices | As long as tax and accounting law requires | [Privacy and your data](https://docs.evohub.io/privacy-and-data.md#how-long-data-is-kept) and [EvoTrail](https://docs.evohub.io/evotrail-overview.md#how-long-data-is-kept) have the full lists, and the [Privacy Notice](https://evohub.io/privacy) takes precedence over both. ## Related - [Troubleshooting](https://docs.evohub.io/troubleshooting.md) - [FAQ](https://docs.evohub.io/faq.md) - [How billing works](https://docs.evohub.io/how-billing-works.md) --- Source: https://docs.evohub.io/alert-ingest.md # Alert ingest API Version 1.0 · OpenAPI 3.1.0 Send alerts into EvoHub On-Call from anything that can make an HTTP request. - **Events** (`POST /ingest/api`) — trigger, acknowledge and resolve alerts with a small JSON body. Compatible with the Events v2 shape, so a tool that already speaks it only changes the URL and key. - **Integration webhooks** (`POST /ingest/{type}`) — point a monitoring tool's own webhook at EvoHub; the body is whatever that tool sends. Every request is authenticated by an **integration key**, shown as **API Key** on the integration in On-Call → Integrations. The key decides the organization and the escalation policy; nothing else in the request can. ## Servers - `https://evohub.io` ## Authentication - `integrationKey`: API key in the query `key` — The integration key (the integration's API Key in On-Call → Integrations). - `bearerKey`: HTTP Bearer — The integration key as a bearer token (`/ingest/api` only). ## Events Trigger, acknowledge and resolve alerts by dedup key. - [POST /ingest/api](https://docs.evohub.io/alert-ingest/send-event.md): Send an event ## Integration webhooks Native webhook payloads from monitoring tools. - [POST /ingest/{type}](https://docs.evohub.io/alert-ingest/send-integration-webhook.md): Receive a monitoring tool's webhook --- Source: https://docs.evohub.io/alert-ingest/send-event.md # Send an event `POST https://evohub.io/ingest/api` Part of the [Alert ingest API](https://docs.evohub.io/alert-ingest.md) reference · operationId `sendEvent`. Triggers, acknowledges or resolves an alert. The integration key may be sent as `routing_key` in the body, as `?key=` in the URL, or as `Authorization: Bearer ` — checked in that order. A trigger needs only a `summary`; everything else has a default. Acknowledge and resolve find the open alert by `dedup_key`; when no open alert has that key, nothing happens and the answer is still 202. A trigger without a `dedup_key` gets one derived from its summary and source, returned in the response — keep it to resolve the alert later. A trigger whose `dedup_key` matches an open alert does not open a second one; it is recorded as a retrigger on the existing alert. ## Authorization Any one of: - `integrationKey` - `bearerKey` - No authentication Where: - `integrationKey`: API key in the query `key` — The integration key (the integration's API Key in On-Call → Integrations). - `bearerKey`: HTTP Bearer — The integration key as a bearer token (`/ingest/api` only). ## Query parameters - `key` (string): The integration key, when it is not sent as `routing_key` or a bearer token. ## Request body (required) Content type: `application/json` Type: `EventRequest` An event. The nested `payload` form and the flat fields are interchangeable; a flat field fills the payload field it names when that one is empty (`title` is another name for `summary`). - `routing_key` (string): The integration key. - `event_action` (string, one of `trigger`, `acknowledge`, `resolve`, default `trigger`) - `dedup_key` (string): Identifies the alert. Required for acknowledge and resolve. - `client` (string): Accepted for Events v2 compatibility; not stored. - `client_url` (string): Accepted for Events v2 compatibility; not stored. - `payload` (EventPayload) - `summary` (string, example `Database primary is down`): The alert's title. Required for a trigger. - `source` (string, example `db-01.prod.acme.example`): Where it happened. Stored as the `source` label. - `severity` (Severity, one of `critical`, `high`, `medium`, `low`, `info`, `error`, `warning`, default `medium`) - `description` (string) - `timestamp` (string (date-time)): Accepted for Events v2 compatibility; the alert is stamped on arrival. - `component` (string): Stored as the `component` label. - `group` (string): Stored as the `group` label. - `class` (string): Stored as the `class` label. - `custom_details` (object): Each entry becomes a label, its value as text. - Other keys: any - `summary` (string): Flat form of `payload.summary`. - `title` (string): Another name for `summary`. - `severity` (Severity, one of `critical`, `high`, `medium`, `low`, `info`, `error`, `warning`, default `medium`) - `source` (string): Flat form of `payload.source`. - `description` (string): The alert's description. ## Responses ### 202 — The event was processed. Content type: `application/json` Type: `EventAccepted` `status`, `message` and `dedup_key` are sent both at the top level (where Events v2 clients read them) and under `data`. - `status` (string, value `success`) - `message` (string, example `Event processed`) - `dedup_key` (string, example `db-primary-down`) - `data` (object, required) - `status` (string, required, value `success`) - `message` (string, required, example `Event processed`) - `dedup_key` (string, required, example `db-primary-down`): The alert's dedup key — the one sent, or the one derived. - `success` (boolean, required, value `true`) ### 400 — The body is not JSON, `summary` is missing on a trigger, `dedup_key` is missing on an acknowledge or resolve, or `event_action` is unknown. Content type: `application/json` Type: `EventRejected` How `/ingest/api` answers a request it did not process: `success` is `false`, and `status`, `message` and `errors` are sent both at the top level and under `data`. - `status` (string, value `invalid event`) - `message` (string, example `summary is required`) - `errors` (array of string) - `data` (object) - `status` (string, value `invalid event`) - `message` (string, example `summary is required`) - `errors` (array of string) - `success` (boolean, value `false`) ### 401 — No integration key was sent, or it names no enabled integration (or one with a signing secret, which only accepts signed deliveries on its own type). Content type: `application/json` Type: `EventRejected` How `/ingest/api` answers a request it did not process: `success` is `false`, and `status`, `message` and `errors` are sent both at the top level and under `data`. - `status` (string, value `invalid event`) - `message` (string, example `summary is required`) - `errors` (array of string) - `data` (object) - `status` (string, value `invalid event`) - `message` (string, example `summary is required`) - `errors` (array of string) - `success` (boolean, value `false`) ### 413 — The body is larger than 1 MiB (`PAYLOAD_TOO_LARGE`, in the `{"error": {code, message}}` shape). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `INVALID_KEY`) - `message` (string, required, example `invalid integration key`) - `details` (array of object): Field problems, on `VALIDATION_FAILED`. - `field` (string) - `message` (string) ### 500 — The alert could not be created, acknowledged or resolved. Content type: `application/json` Type: `EventRejected` How `/ingest/api` answers a request it did not process: `success` is `false`, and `status`, `message` and `errors` are sent both at the top level and under `data`. - `status` (string, value `invalid event`) - `message` (string, example `summary is required`) - `errors` (array of string) - `data` (object) - `status` (string, value `invalid event`) - `message` (string, example `summary is required`) - `errors` (array of string) - `success` (boolean, value `false`) ### 503 — The key could not be checked right now. Retry after the delay given. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `EventRejected` How `/ingest/api` answers a request it did not process: `success` is `false`, and `status`, `message` and `errors` are sent both at the top level and under `data`. - `status` (string, value `invalid event`) - `message` (string, example `summary is required`) - `errors` (array of string) - `data` (object) - `status` (string, value `invalid event`) - `message` (string, example `summary is required`) - `errors` (array of string) - `success` (boolean, value `false`) ## Example request ```bash curl -X POST 'https://evohub.io/ingest/api?key=' \ -H 'Content-Type: application/json' \ -d '{ "summary": "Database primary is down", "routing_key": "YOUR_INTEGRATION_KEY" }' ``` --- Source: https://docs.evohub.io/alert-ingest/send-integration-webhook.md # Receive a monitoring tool's webhook `POST https://evohub.io/ingest/{type}` Part of the [Alert ingest API](https://docs.evohub.io/alert-ingest.md) reference · operationId `sendIntegrationWebhook`. The endpoint each integration's webhook URL points at, shown with its key in On-Call → Integrations. `type` selects the parser. For `alerts` the body is EvoHub's generic alert (below). For every other type it is the tool's own webhook payload, sent unchanged — a firing alert opens an alert, and where the tool reports recovery, the matching open alert is resolved. Most tools send JSON; UptimeRobot, StatusCake, Site24x7 and PRTG may send form-encoded data (UptimeRobot and StatusCake may also append fields to the URL's query string), and those are read too. Answers **200** whenever the payload was read, even if it produced no alert, so tools do not disable the webhook; `created` and `resolved` say what happened. An AWS SNS subscription confirmation sent to `cloudwatch` or `eventbridge` is confirmed automatically and answered with `created` 0 and a `note`. SNS deliveries must carry a valid SNS signature. `github`, `gitlab`, `jenkins` and `sonarqube` deliveries can be signed: once the integration has a signing secret (On-Call → Integrations, or the On-Call API's signing-secret endpoints), a delivery without a valid signature is refused. GitHub signs with `X-Hub-Signature-256`, SonarQube with `X-Sonar-Webhook-HMAC-SHA256`, GitLab sends the secret as `X-Gitlab-Token` or signs with its signing token, and Jenkins sends it as the Basic auth password or `X-EvoHub-Token`. Two more routes live under `/ingest`: `/ingest/api` (the Events API, above) and `/ingest/email`, where EvoHub's mail provider delivers the mail sent to an Email integration's address. You send email to that address; you never call `/ingest/email` yourself. ## Authorization Requires: - `integrationKey` Where: - `integrationKey`: API key in the query `key` — The integration key (the integration's API Key in On-Call → Integrations). ## Path parameters - `type` (string, required, one of `alerts`, `prometheus`, `grafana`, `datadog`, `newrelic`, `cloudwatch`, `azuremonitor`, `dynatrace`, `sentry`, `googlecloud`, `zabbix`, `uptimerobot`, `pingdom`, `site24x7`, `statuscake`, `nagios`, `prtg`, `cortex`, `opensearch`, `uptimekuma`, `graylog`, `splunk`, `cloudflare`, `instana`, `netdata`, `eventbridge`, `elastic`, `opmanager`, `solarwinds`, `sumologic`, example `alerts`): The integration type. ## Query parameters - `key` (string, required, example `YOUR_INTEGRATION_KEY`): The integration key. `eventbridge` also accepts it in an `X-EvoHub-Key` header and `honeycomb` in `X-Honeycomb-Webhook-Token`. ## Request body (required) Content type: `application/json` Type: `GenericAlert | object` - One of: - GenericAlert - `title` (string, required, example `Disk almost full on web-03`) - `description` (string) - `status` (string): `resolved` (or `resolve`, `ok`, `up`, `online`, `recovered`, `closed`) resolves the open alert with this fingerprint; anything else, or nothing, opens or retriggers one. - `severity` (string, one of `critical`, `high`, `medium`, `low`, `info`, default `medium`): Common synonyms are read too (`error` and `major` as `high`, `warning` as `medium`, `minor` as `low`, `p1`–`p5`); anything else is `medium`. - `source` (string): Stored as the alert's `source` label; the alert's own source is always `webhook`. - `fingerprint` (string): Deduplicates repeated sends while the alert is open. When omitted, one is derived from the title, the labels and `alert_instance`/`alert_service`. - `escalation_policy_id` (string): Used only when the integration has no escalation policy of its own. - `labels` (object) - Other keys: string - `annotations` (object) - Other keys: string - Native payload: The monitoring tool's own webhook body, for every type except `alerts`. - Other keys: any Content type: `application/x-www-form-urlencoded` Type: `object` Form-encoded bodies sent by `uptimerobot`, `statuscake`, `site24x7` and `prtg`. - Other keys: string ## Responses ### 200 — The payload was read. Content type: `application/json` Type: `IngestResult` - `data` (object) - `received` (integer): Alerts and recoveries found in the payload. - `created` (integer): Alerts opened, or matched to an alert already open with the same fingerprint. - `resolved` (integer): Recoveries processed. - `test` (boolean): Present and `true` when the delivery was the tool's test notification. - `note` (string): Present when the delivery needed no alert work, for example a test notification, an AWS SNS subscription confirmation, or a delivery already processed. - `success` (boolean, value `true`) ### 400 — The body could not be read as this type's payload (`INVALID_BODY`); for `azuremonitor`, the common alert schema is not enabled (`INVALID_SCHEMA`); for `alerts`, `title` is missing, or a resolve has neither `fingerprint` nor `title` (`VALIDATION_FAILED`, with `details`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `INVALID_KEY`) - `message` (string, required, example `invalid integration key`) - `details` (array of object): Field problems, on `VALIDATION_FAILED`. - `field` (string) - `message` (string) ### 401 — `key` is missing, unknown, or names a disabled integration (`INVALID_KEY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `INVALID_KEY`) - `message` (string, required, example `invalid integration key`) - `details` (array of object): Field problems, on `VALIDATION_FAILED`. - `field` (string) - `message` (string) ### 403 — For `github`, `gitlab`, `jenkins` and `sonarqube`: the integration has a signing secret and the delivery carries no signature, or one that does not verify (`INVALID_SIGNATURE`). For `cloudwatch` and `eventbridge`: an SNS message whose SNS signature does not verify (`INVALID_SIGNATURE`). On any other type: the key belongs to an integration with a signing secret, which only accepts signed deliveries on its own type (`SIGNATURE_REQUIRED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `INVALID_KEY`) - `message` (string, required, example `invalid integration key`) - `details` (array of object): Field problems, on `VALIDATION_FAILED`. - `field` (string) - `message` (string) ### 413 — The body is larger than 1 MiB (`PAYLOAD_TOO_LARGE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `INVALID_KEY`) - `message` (string, required, example `invalid integration key`) - `details` (array of object): Field problems, on `VALIDATION_FAILED`. - `field` (string) - `message` (string) ### 500 — The alert could not be stored (`INTERNAL_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `INVALID_KEY`) - `message` (string, required, example `invalid integration key`) - `details` (array of object): Field problems, on `VALIDATION_FAILED`. - `field` (string) - `message` (string) ### 503 — The key, the integration's signing secret, or an SNS message's signature could not be checked right now (`INGEST_UNAVAILABLE`). Retry after the delay given. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `INVALID_KEY`) - `message` (string, required, example `invalid integration key`) - `details` (array of object): Field problems, on `VALIDATION_FAILED`. - `field` (string) - `message` (string) ## Example request ```bash curl -X POST 'https://evohub.io/ingest/alerts?key=' \ -H 'Content-Type: application/json' \ -d '{ "title": "Disk almost full on web-03", "labels": { "host": "web-03.acme.example" }, "source": "cron-disk-check", "severity": "high", "description": "/var is at 93%", "fingerprint": "web-03-disk-var" }' ``` --- Source: https://docs.evohub.io/billing.md # Billing API Version 1.0 · OpenAPI 3.1.0 Read what your organization uses and owes from scripts and finance tools: the month's usage and projection, itemised usage, monthly statements, payments, the credit balance, the rate card, and the term commitment. Keep the billing details invoices are made out to up to date, and manage saved cards. EvoHub is billed by usage, measured in **EHU** (EvoHub Units). Each metric has an EHU weight on the rate card, and each pricing tier a price per EHU, so every charge can be reproduced by hand. Each month's free allowance is applied before anything is charged. **Authentication.** Every endpoint except the published price list (`/api/v1/pricing`) and the current campaign (`/api/v1/campaigns/active`) takes an EvoHub API key, sent as `Authorization: Bearer evohub_…` or as `X-API-Key: evohub_…`. Each operation names the scope the key needs in its description and in `x-evohub-permission`. A missing or invalid key gets **401**; a valid key without the scope gets **403** `FORBIDDEN`, never 401. See the [API overview](/api-overview), [API keys and scopes](/api-keys-and-scopes) and [Errors](/errors). **What a key cannot do.** Adding a card and buying credit both end with the card being confirmed in the browser, inside the console's secure card form; they are listed in `x-route-check.not-public` and are done in the console. **Responses.** A successful answer carries its result in `data`. Errors carry an `error` object with a machine-readable `code`, a `message` and a `request_id` (also sent in the `X-Request-ID` header — quote it to support). Amounts are in US dollars (`_usd`) or EHU (`_ehu`). Timestamps are RFC 3339 in UTC; dates are `YYYY-MM-DD` and billing periods are UTC calendar months. **Payments not enabled.** Endpoints that read or change cards and billing details answer **503** `PAYMENTS_DISABLED` when payments are not enabled for the environment. ## Servers - `https://evohub.io` ## Authentication - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Usage This month's usage, projection and allowance, and itemised usage over any range. - [GET /api/v1/billing/summary](https://docs.evohub.io/billing/get-billing-summary.md): Get this month's summary - [GET /api/v1/billing/usage](https://docs.evohub.io/billing/get-usage.md): Get itemised usage ## Statements Monthly bills and everything charged to the organization. - [GET /api/v1/billing/statements](https://docs.evohub.io/billing/list-statements.md): List statements - [GET /api/v1/billing/statements/{period}](https://docs.evohub.io/billing/get-statement.md): Get one statement - [GET /api/v1/billing/payments](https://docs.evohub.io/billing/list-payments.md): List payments ## Rate card The EHU weight of every metric and the price per EHU. - [GET /api/v1/billing/rate-card](https://docs.evohub.io/billing/get-rate-card.md): Get the rate card in force - [GET /api/v1/pricing](https://docs.evohub.io/billing/get-pricing.md): Get published pricing ## Credit The prepaid EHU balance and its history. - [GET /api/v1/billing/credit](https://docs.evohub.io/billing/get-credit.md): Get the credit balance ## Payment methods Cards saved for the organization. - [GET /api/v1/billing/payment-methods](https://docs.evohub.io/billing/list-payment-methods.md): List saved cards - [POST /api/v1/billing/payment-methods/{id}/default](https://docs.evohub.io/billing/set-default-payment-method.md): Make a card the default - [DELETE /api/v1/billing/payment-methods/{id}](https://docs.evohub.io/billing/remove-payment-method.md): Remove a card ## Billing details Who invoices are made out to, and the tax registration. - [GET /api/v1/billing/profile](https://docs.evohub.io/billing/get-billing-profile.md): Get billing details - [PUT /api/v1/billing/profile](https://docs.evohub.io/billing/set-billing-profile.md): Set billing details - [GET /api/v1/billing/tax-details](https://docs.evohub.io/billing/get-tax-details.md): Get tax details - [PUT /api/v1/billing/tax-details](https://docs.evohub.io/billing/set-tax-id.md): Set the tax number ## Commitment A monthly volume agreed for a fixed term, at a lower price per EHU. - [GET /api/v1/billing/commitment](https://docs.evohub.io/billing/get-commitment.md): Get the commitment - [POST /api/v1/billing/commitment](https://docs.evohub.io/billing/accept-commitment.md): Enter a commitment - [DELETE /api/v1/billing/commitment](https://docs.evohub.io/billing/end-commitment.md): End the commitment early ## Campaigns Credit campaigns and where the organization stands with them. - [GET /api/v1/billing/campaign](https://docs.evohub.io/billing/get-campaign-status.md): Get the organization's campaign standing - [GET /api/v1/campaigns/active](https://docs.evohub.io/billing/get-active-campaign.md): Get the running campaign --- Source: https://docs.evohub.io/billing/get-billing-summary.md # Get this month's summary `GET https://evohub.io/api/v1/billing/summary` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `getBillingSummary`. The headline numbers for the current UTC month: usage so far, the month-end projection, what usage so far would cost, and where the free allowance and payment state stand. - `mtd_ehu` is the billable usage so far; `projected_ehu` adds the run rate of the last seven complete days for the rest of the month. - `projected_usd` and `charged_usd` are already net of the free allowance and of credit — do not subtract them again. - `charged_usd` breaks down as `allowance_covered_ehu` (absorbed by the free allowance), `credit_applied_ehu` (covered by credit) and `overage_ehu` (charged to the card), out of `chargeable_ehu`. - `dispatch_blocked` is `true` when outbound notifications are stopped because the allowance is spent and there is neither a card nor credit; `dispatch_blocked_at` and `dispatch_block_reason` then say since when and why. While notifications are still sent but the same condition holds, `dispatch_grace_ends_at` is when they stop. Requires the `billing:usage:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The summary. Content type: `application/json` Type: `object` - `data` (Summary) - `period_start` (string (date)): The first day of the current month. - `period_end` (string (date)): The first day of next month (exclusive). - `mtd_ehu` (number): Billable usage so far this month. - `usage_ehu` (number): The same usage, summed per line. - `projected_ehu` (number): Forecast usage for the whole month. - `projected_usd` (number): Forecast charge for the month, net of the free allowance and credit. - `days_elapsed` (integer): Complete days the run rate is read from. - `days_remaining` (integer) - `charged_usd` (number): What usage so far would cost, net of the free allowance and credit. - `allowance_covered_ehu` (number) - `credit_applied_ehu` (number) - `chargeable_ehu` (number): Usage the free allowance did not cover, before credit. - `overage_ehu` (number): Usage left to charge to the card after the allowance and credit — the EHU behind `charged_usd`. - `commitment_ehu` (number): The committed volume billed this month; 0 without a commitment. - `status` (string, one of `active`, `suspended`) - `pricing_tier` (string, one of `on_demand`, `commitment`, `prepaid`) - `allowance_ehu` (number): This month's free allowance. - `allowance_used_ehu` (number) - `allowance_remaining_ehu` (number) - `has_payment_method` (boolean) - `dispatch_blocked` (boolean): Whether outbound notifications are stopped right now. - `dispatch_blocked_at` (string (date-time)): Present only while `dispatch_blocked` is `true`. - `dispatch_block_reason` (string, value `allowance_spent_no_card_no_credit`): Present only while `dispatch_blocked` is `true`. - `dispatch_grace_ends_at` (string (date-time)): When notifications stop if nothing changes. Present only while the allowance is spent with no card and no credit, and notifications are still being sent. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — The summary could not be computed (`SUMMARY_FAILED`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/summary' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/get-usage.md # Get itemised usage `GET https://evohub.io/api/v1/billing/usage` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `getUsage`. Usage per metric over a date range — by default the current UTC month. `from` is inclusive and `to` is exclusive, so `from=2026-10-01&to=2026-11-01` is October. With `group_by=day`, the answer has one entry per day that has usage, oldest first, each with its total EHU and its metrics. Otherwise it has one row per metric for the whole range. Requires the `billing:usage:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `from` (string (date), example `2026-10-01`): First day, `YYYY-MM-DD`. Default the first day of the current month. - `to` (string (date), example `2026-11-01`): The day after the last one, `YYYY-MM-DD`. Default the first day of next month. - `group_by` (string, one of `metric`, `day`): `day` for a per-day breakdown; anything else (or nothing) totals per metric. ## Responses ### 200 — The usage. Content type: `application/json` Type: `object` - `data` (UsageByMetric | UsageByDay) - One of: - UsageByMetric - `from` (string (date)) - `to` (string (date)) - `metrics` (array of UsageRow) - `metric_key` (string, example `oncall.alert_ingested`) - `quantity` (number): How many of the metric's units were used. - `ehu` (number): Their EHU, exact. - UsageByDay - `from` (string (date)) - `to` (string (date)) - `days` (array of object) - `day` (string (date)) - `ehu` (number) - `metrics` (array of UsageRow) - `metric_key` (string, example `oncall.alert_ingested`) - `quantity` (number): How many of the metric's units were used. - `ehu` (number): Their EHU, exact. ### 400 — `from` or `to` is not `YYYY-MM-DD` (`INVALID_FROM`, `INVALID_TO`), or `to` is not after `from` (`INVALID_RANGE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — Usage could not be loaded (`USAGE_FAILED`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/usage' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/list-statements.md # List statements `GET https://evohub.io/api/v1/billing/statements` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `listStatements`. The organization's monthly statements, newest period first, without their lines (read one statement for those). `total_usd` is the period's own usage; `amount_due_usd` is what the statement actually asks for, adding amounts carried in from earlier months that were too small to charge on their own. Requires the `billing:invoice:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `limit` (integer, default `24`, min 1, max 100): How many statements, 1–100. Any other value means 24. ## Responses ### 200 — The statements. Content type: `application/json` Type: `object` - `data` (object) - `statements` (array of Statement) - `id` (string) - `org_id` (string) - `period_start` (string (date-time)) - `period_end` (string (date-time)): The first instant of the next month (exclusive). - `status` (string, one of `open`, `predicted`, `finalized`): `open` for the month in progress, `predicted` for a closed month computed but not yet charged, `finalized` once the amount is frozen. - `rate_version` (integer): The rate card version that priced the period. - `pricing_tier` (string, one of `on_demand`, `commitment`, `prepaid`) - `total_ehu` (number): Exact usage for the period, before rounding and the free allowance. - `billed_ehu` (number): `total_ehu` rounded up to a whole EHU. - `allowance_ehu` (number): How much the free allowance covered. - `chargeable_ehu` (number): `billed_ehu` minus `allowance_ehu`. - `credit_ehu` (number): How much of that was paid from the credit balance. - `commitment_ehu` (number): The committed volume billed, used or not. - `overage_ehu` (number): Usage beyond what the tier covered, priced on demand. - `total_usd` (number): The period's own charge. A month covered by the allowance costs 0. - `carried_in_usd` (number): Amounts owed by earlier months that were too small to charge alone, collected with this one. - `carried_periods` (array of string (date-time)): The months carried in. Left out when there are none. - `settled_by_period` (string (date-time)): The month whose payment collected this one, when it was carried forward. Left out while still owed. - `generated_at` (string (date-time)) - `finalized_at` (string (date-time)): When the amount was frozen. Left out while it can still change. - `charge_status` (string, one of `not_required`, `below_minimum`, `no_payment_method`, `invoiced`, `paid`, `failed`): What became of the money. Left out until collection has run. `not_required`: nothing was owed. `below_minimum`: too small to charge, carried forward. `no_payment_method`: owed with no card on file. `invoiced`: invoice raised and payment attempted. `paid`. `failed`: the card was declined or the invoice refused. - `charged_at` (string (date-time)) - `hosted_invoice_url` (string (uri)) - `invoice_pdf_url` (string (uri)) - `receipt_url` (string (uri)) - `lines` (array of object): One line per metric. Only on a single statement, and left out when it has none. - `metric_key` (string) - `quantity` (number) - `ehu` (number) - `ehu_billed` (number) - `usd` (number) - `amount_due_usd` (number): What the statement asks for — `total_usd` plus `carried_in_usd`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — Statements could not be loaded (`STATEMENTS_FAILED`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/statements' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/get-statement.md # Get one statement `GET https://evohub.io/api/v1/billing/statements/{period}` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `getStatement`. One month's statement with its lines, one per metric. A line's `ehu_billed` is its exact EHU; the period total is rounded up to a whole EHU once, in `billed_ehu`. Requires the `billing:invoice:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `period` (string, required, pattern `^\d{4}-\d{2}$`, example `2026-10`): The month, `YYYY-MM`. ## Responses ### 200 — The statement. Content type: `application/json` Type: `object` - `data` (Statement) - `id` (string) - `org_id` (string) - `period_start` (string (date-time)) - `period_end` (string (date-time)): The first instant of the next month (exclusive). - `status` (string, one of `open`, `predicted`, `finalized`): `open` for the month in progress, `predicted` for a closed month computed but not yet charged, `finalized` once the amount is frozen. - `rate_version` (integer): The rate card version that priced the period. - `pricing_tier` (string, one of `on_demand`, `commitment`, `prepaid`) - `total_ehu` (number): Exact usage for the period, before rounding and the free allowance. - `billed_ehu` (number): `total_ehu` rounded up to a whole EHU. - `allowance_ehu` (number): How much the free allowance covered. - `chargeable_ehu` (number): `billed_ehu` minus `allowance_ehu`. - `credit_ehu` (number): How much of that was paid from the credit balance. - `commitment_ehu` (number): The committed volume billed, used or not. - `overage_ehu` (number): Usage beyond what the tier covered, priced on demand. - `total_usd` (number): The period's own charge. A month covered by the allowance costs 0. - `carried_in_usd` (number): Amounts owed by earlier months that were too small to charge alone, collected with this one. - `carried_periods` (array of string (date-time)): The months carried in. Left out when there are none. - `settled_by_period` (string (date-time)): The month whose payment collected this one, when it was carried forward. Left out while still owed. - `generated_at` (string (date-time)) - `finalized_at` (string (date-time)): When the amount was frozen. Left out while it can still change. - `charge_status` (string, one of `not_required`, `below_minimum`, `no_payment_method`, `invoiced`, `paid`, `failed`): What became of the money. Left out until collection has run. `not_required`: nothing was owed. `below_minimum`: too small to charge, carried forward. `no_payment_method`: owed with no card on file. `invoiced`: invoice raised and payment attempted. `paid`. `failed`: the card was declined or the invoice refused. - `charged_at` (string (date-time)) - `hosted_invoice_url` (string (uri)) - `invoice_pdf_url` (string (uri)) - `receipt_url` (string (uri)) - `lines` (array of object): One line per metric. Only on a single statement, and left out when it has none. - `metric_key` (string) - `quantity` (number) - `ehu` (number) - `ehu_billed` (number) - `usd` (number) - `amount_due_usd` (number): What the statement asks for — `total_usd` plus `carried_in_usd`. ### 400 — `period` is not `YYYY-MM` (`INVALID_PERIOD`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 404 — There is no statement for that month (`STATEMENT_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — The statement could not be loaded (`STATEMENT_FAILED`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/statements/2026-10' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/list-payments.md # List payments `GET https://evohub.io/api/v1/billing/payments` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `listPayments`. Everything charged to the organization, newest first: credit bundles bought (`bundle`), monthly usage bills that raised money (`usage`), and early-termination charges for a commitment (`commitment_clawback`). A month covered entirely by the free allowance is not a payment and is not listed. Requires the `billing:payment:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The payments. Content type: `application/json` Type: `object` - `data` (object) - `payments` (array of Payment) - `kind` (string, one of `bundle`, `usage`, `commitment_clawback`) - `at` (string (date-time)) - `usd` (number) - `ehu` (number): EHU bought, for a bundle. Left out otherwise. - `period` (string): The month a usage bill covers, `YYYY-MM`. Left out otherwise. - `status` (string): `paid` for a bundle, `invoiced` for an early-termination charge, the statement's `charge_status` for a usage bill. - `invoice_url` (string (uri)) - `receipt_url` (string (uri)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — Payments could not be loaded (`PAYMENTS_UNAVAILABLE`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/payments' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/get-rate-card.md # Get the rate card in force `GET https://evohub.io/api/v1/billing/rate-card` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `getRateCard`. The rate card that prices today's usage: one EHU weight per metric and the price per EHU at each tier. A metric's charge is `quantity × ehu_weight` EHU, priced at the organization's tier. This can differ from `/api/v1/pricing`, which shows the latest published card — a new price is published ahead of the day it takes effect. Requires no scope: any valid key can read it. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The rate card. Content type: `application/json` Type: `object` - `data` (RateCard) - `version` (integer) - `effective_from` (string (date-time)): When this card starts pricing usage. - `usd_per_ehu` (object): The price of one EHU at each tier, in US dollars. - `on_demand` (number) - `commitment` (number) - `prepaid` (number): The price of credit bought in bundles. - `metrics` (array of object): One weight per metric, sorted by `metric_key`. - `metric_key` (string) - `ehu_weight` (number): EHU charged for one unit of the metric. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — The rate card could not be loaded (`RATE_CARD_FAILED`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/rate-card' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/get-pricing.md # Get published pricing `GET https://evohub.io/api/v1/pricing` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `getPricing`. The latest published rate card, with the monthly free allowance that goes with it — the price list the pricing page shows. When a new price has been announced for a future date, this already shows it; the rate card in force today is at `/api/v1/billing/rate-card`. No API key is needed. Answers may be cached for five minutes. ## Responses ### 200 — The published pricing. Headers: - `Cache-Control` (string): `public, max-age=300`. Content type: `application/json` Type: `object` - `data` (RateCard & object) - `version` (integer) - `effective_from` (string (date-time)): When this card starts pricing usage. - `usd_per_ehu` (object): The price of one EHU at each tier, in US dollars. - `on_demand` (number) - `commitment` (number) - `prepaid` (number): The price of credit bought in bundles. - `metrics` (array of object): One weight per metric, sorted by `metric_key`. - `metric_key` (string) - `ehu_weight` (number): EHU charged for one unit of the metric. - `free_tier_ehu_per_month` (number): The free allowance every organization gets each month, in EHU. ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — The rate card could not be loaded (`RATE_CARD_FAILED`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pricing' ``` --- Source: https://docs.evohub.io/billing/get-credit.md # Get the credit balance `GET https://evohub.io/api/v1/billing/credit` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `getCredit`. The organization's prepaid EHU balance, the price per EHU for a bundle (after any offer agreed for the organization), the smallest bundle that can be bought, the suggested bundle sizes, and the last 50 movements on the balance, newest first. Credit is used before the card: a statement draws on it after the free allowance. Bought credit never expires; credit given by EvoHub (a campaign or a goodwill grant) may, at `expires_at`. `can_purchase` is `false` while there is no saved card. Bundles are bought in the console. Requires the `billing:usage:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The credit balance. Content type: `application/json` Type: `object` - `data` (CreditSummary) - `balance_ehu` (number) - `usd_per_ehu` (number): The bundle price per EHU. - `minimum_ehu` (number): The smallest bundle that can be bought. - `presets` (array of number): Suggested bundle sizes, in EHU. - `entries` (array of CreditEntry): The last 50 movements, newest first. - `id` (integer) - `org_id` (string) - `kind` (string, one of `purchase`, `grant`, `consumption`, `adjustment`): `purchase`: a bundle bought. `grant`: credit given by EvoHub. `consumption`: a statement drew on the balance (negative). `adjustment`: a correction. - `ehu` (number): Positive adds to the balance, negative spends it. - `usd_paid` (number): What was paid, for a purchase. - `reason` (string) - `granted_by` (string): Who at EvoHub gave a grant. Left out for anything automatic. - `stripe_ref` (string): The payment a purchase was made with. - `receipt_url` (string (uri)) - `invoice_url` (string (uri)) - `invoice_pdf_url` (string (uri)) - `period_start` (string (date-time)): The statement that consumed it, for a consumption. - `expires_at` (string (date-time)): When given credit stops counting. Bought credit never expires. - `created_at` (string (date-time)) - `can_purchase` (boolean): `false` while no card is saved. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — The balance could not be loaded (`CREDIT_UNAVAILABLE`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/credit' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/list-payment-methods.md # List saved cards `GET https://evohub.io/api/v1/billing/payment-methods` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `listPaymentMethods`. The cards saved for the organization. `is_default` marks the one invoices are charged to. `cards` is `[]` when none has been saved. Cards are added in the console. Requires the `billing:payment:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The saved cards. Content type: `application/json` Type: `object` - `data` (object) - `cards` (array of Card) - `id` (string) - `brand` (string, example `visa`) - `last4` (string) - `exp_month` (integer) - `exp_year` (integer) - `is_default` (boolean): Whether invoices are charged to this card. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 502 — The saved cards could not be loaded from the payment provider (`CARDS_UNAVAILABLE`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 503 — Payments are not enabled on this environment (`PAYMENTS_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/payment-methods' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/set-default-payment-method.md # Make a card the default `POST https://evohub.io/api/v1/billing/payment-methods/{id}/default` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `setDefaultPaymentMethod`. Makes a saved card the one invoices are charged to. No request body. Requires the `billing:payment:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `pm_1QExampleMc4444`): The card's ID, from the list of saved cards. ## Responses ### 200 — The card is now the default. Content type: `application/json` Type: `object` - `data` (object) - `default_payment_method` (string): The card's ID. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 404 — The card is not saved for this organization (`CARD_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 502 — The payment provider did not accept the change (`CARD_UPDATE_FAILED`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 503 — Payments are not enabled on this environment (`PAYMENTS_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/billing/payment-methods/pm_1QExampleMc4444/default' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/remove-payment-method.md # Remove a card `DELETE https://evohub.io/api/v1/billing/payment-methods/{id}` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `removePaymentMethod`. Removes a saved card. If it was the default and other cards remain, the first remaining card becomes the default first. `remaining` is how many cards are left. With no card left and no credit, the organization is charged nothing more but, once the month's free allowance is spent, its outbound notifications stop after a grace period (see `dispatch_grace_ends_at` on the summary). Requires the `billing:payment:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `pm_1QExampleMc4444`): The card's ID, from the list of saved cards. ## Responses ### 200 — The card was removed. Content type: `application/json` Type: `object` - `data` (object) - `remaining` (integer): Cards still saved. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 404 — The card is not saved for this organization (`CARD_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 502 — The payment provider did not accept the change (`CARD_UPDATE_FAILED`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 503 — Payments are not enabled on this environment (`PAYMENTS_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/billing/payment-methods/pm_1QExampleMc4444' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/get-billing-profile.md # Get billing details `GET https://evohub.io/api/v1/billing/profile` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `getBillingProfile`. Who the organization's invoices are made out to: the legal name, the email invoices and receipts go to, the address, and the tax number. `complete` says whether enough is on file for a proper invoice; `tax_id_supported` says whether a tax number can be filed for this country and entity type. Every field is empty while no billing details or card have been saved. Requires the `billing:profile:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The billing details. Content type: `application/json` Type: `object` - `data` (BillingProfile) - `entity_type` (string, one of `business`, `individual`) - `legal_name` (string): The company or person invoices are made out to. - `email` (string): Where invoices and receipts are sent. - `address` (BillingAddress) - `line1` (string) - `line2` (string): Left out when empty. - `city` (string) - `state` (string): Left out when empty. - `postal_code` (string) - `country` (string, example `IE`): Two-letter country code (ISO 3166-1 alpha-2). - `tax_id` (string) - `tax_id_type` (string): What the number is filed as, e.g. `eu_vat`. Left out when there is no number. - `tax_id_supported` (boolean): Whether a tax number can be filed for this country and entity type. - `complete` (boolean): Whether enough is on file for a proper invoice. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 502 — The billing details could not be loaded from the payment provider (`PROFILE_UNAVAILABLE`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 503 — Payments are not enabled on this environment (`PAYMENTS_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/profile' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/set-billing-profile.md # Set billing details `PUT https://evohub.io/api/v1/billing/profile` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `setBillingProfile`. Replaces the billing details as a whole — send every field, not just the ones that changed. The address is written as given and replaces any address taken from a card. Surrounding spaces are trimmed and the country is upper-cased. - `legal_name`, `email`, `address.line1`, `address.city` and a two-letter `address.country` are required; otherwise **400** `INCOMPLETE_PROFILE`, whose message names what is missing. - `entity_type` is `business` or `individual`; anything else is taken as `business`. An individual has no tax number: one on file is removed and `tax_id` is ignored. - `tax_id` replaces the number on file, filed under the type its country uses; an empty value removes it. A number for a country where none can be filed is **400** `TAX_ID_NOT_SUPPORTED`. Answers with the details as saved. Requires the `billing:profile:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `BillingProfileRequest` - `entity_type` (string, one of `business`, `individual`, default `business`) - `legal_name` (string, required) - `email` (string, required) - `address` (object, required) - `line1` (string, required) - `line2` (string) - `city` (string, required) - `state` (string) - `postal_code` (string) - `country` (string, required, min length 2, max length 2) - `tax_id` (string): Ignored for an individual. `""` removes the number on file. ## Responses ### 200 — The billing details as saved. Content type: `application/json` Type: `object` - `data` (BillingProfile) - `entity_type` (string, one of `business`, `individual`) - `legal_name` (string): The company or person invoices are made out to. - `email` (string): Where invoices and receipts are sent. - `address` (BillingAddress) - `line1` (string) - `line2` (string): Left out when empty. - `city` (string) - `state` (string): Left out when empty. - `postal_code` (string) - `country` (string, example `IE`): Two-letter country code (ISO 3166-1 alpha-2). - `tax_id` (string) - `tax_id_type` (string): What the number is filed as, e.g. `eu_vat`. Left out when there is no number. - `tax_id_supported` (boolean): Whether a tax number can be filed for this country and entity type. - `complete` (boolean): Whether enough is on file for a proper invoice. ### 400 — The body is not JSON (`INVALID_BODY`), a required field is missing (`INCOMPLETE_PROFILE`), a tax number was given for a country where none can be filed (`TAX_ID_NOT_SUPPORTED`), or the details were not accepted — usually a tax number that does not match its country's format (`PROFILE_REJECTED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 503 — Payments are not enabled on this environment (`PAYMENTS_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/billing/profile' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "email": "finance@acme.example", "tax_id": "IE1234567T", "address": { "city": "Dublin", "line1": "1 Market Street", "country": "IE", "postal_code": "D02 XY45" }, "legal_name": "Acme Ltd", "entity_type": "business" }' ``` --- Source: https://docs.evohub.io/billing/get-tax-details.md # Get tax details `GET https://evohub.io/api/v1/billing/tax-details` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `getTaxDetails`. The country the organization is taxed in and the tax number on file. The country comes from the billing address, or from the saved card when no address has been given. `editable` says whether a tax number can be filed for that country. Every field is empty while no billing details or card have been saved. Requires the `billing:profile:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The tax details. Content type: `application/json` Type: `object` - `data` (TaxDetails) - `country` (string) - `tax_id` (string) - `tax_id_type` (string): Left out when there is no number. - `editable` (boolean): Whether a tax number can be filed for this country. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 502 — The tax details could not be loaded from the payment provider (`TAX_DETAILS_UNAVAILABLE`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 503 — Payments are not enabled on this environment (`PAYMENTS_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/tax-details' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/set-tax-id.md # Set the tax number `PUT https://evohub.io/api/v1/billing/tax-details` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `setTaxId`. Replaces the tax number on file, filed under the type the organization's country uses. An empty `tax_id` removes it. To change the address or entity type too, set the billing details instead. Answers with the tax details as saved. Requires the `billing:profile:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `tax_id` (string): The tax registration number, or `""` to remove it. ## Responses ### 200 — The tax details as saved. Content type: `application/json` Type: `object` - `data` (TaxDetails) - `country` (string) - `tax_id` (string) - `tax_id_type` (string): Left out when there is no number. - `editable` (boolean): Whether a tax number can be filed for this country. ### 400 — The body is not JSON (`INVALID_BODY`), or the number was not accepted — usually one that does not match its country's format (`TAX_ID_REJECTED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 409 — No billing details or card have been saved yet (`NO_PAYMENT_SETUP`), or a number was given while the organization's country is not known (`COUNTRY_UNKNOWN`) — set the billing details first. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 503 — Payments are not enabled on this environment (`PAYMENTS_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/billing/tax-details' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "tax_id": "IE1234567T" }' ``` --- Source: https://docs.evohub.io/billing/get-commitment.md # Get the commitment `GET https://evohub.io/api/v1/billing/commitment` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `getCommitment`. What is on offer and what is running: the agreement (`terms`, left out while commitments are not offered), the commitment in force (`active`, left out when there is none), when a commitment agreed today would start (`starts_on`, the first day of next month), the commitment and on-demand prices per EHU for the organization, what ending the active commitment today would cost (`estimated_clawback_usd`), and the last 24 commitments. Requires the `billing:usage:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The commitment state. Content type: `application/json` Type: `object` - `data` (CommitmentState) - `terms` (CommitmentTerms) - `version` (integer) - `body` (string): The full text of the agreement. - `body_sha256` (string) - `term_months` (integer) - `published_at` (string (date-time)) - `active` (Commitment) - `id` (integer) - `org_id` (string) - `ehu_per_month` (number) - `starts_on` (string (date-time)): The first day of the first committed month. - `ends_on` (string (date-time)): The first day after the term (exclusive). - `status` (string, one of `active`, `completed`, `terminated`) - `terms_version` (integer) - `terms_sha256` (string): The hash of the agreement text that was accepted. - `accepted_at` (string (date-time)) - `accepted_by` (string): The person, or the organization key, that accepted. - `accepted_ip` (string) - `terminated_at` (string (date-time)) - `terminated_from` (string (date-time)): The first month no longer committed, for an ended commitment. - `terminated_by` (string) - `clawback_usd` (number): The early-termination charge. - `clawback_invoice_id` (string) - `clawback_invoice_url` (string (uri)) - `clawback_invoice_pdf_url` (string (uri)) - `clawback_invoiced_at` (string (date-time)) - `ending_notice_at` (string (date-time)): When the organization was told the term is nearly over. - `created_at` (string (date-time)) - `starts_on` (string (date-time)): When a commitment agreed now would start. - `estimated_clawback_usd` (number): What ending the active commitment today would cost; 0 without one. - `commitment_usd_per_ehu` (number) - `on_demand_usd_per_ehu` (number) - `history` (array of Commitment): The last 24 commitments. - `id` (integer) - `org_id` (string) - `ehu_per_month` (number) - `starts_on` (string (date-time)): The first day of the first committed month. - `ends_on` (string (date-time)): The first day after the term (exclusive). - `status` (string, one of `active`, `completed`, `terminated`) - `terms_version` (integer) - `terms_sha256` (string): The hash of the agreement text that was accepted. - `accepted_at` (string (date-time)) - `accepted_by` (string): The person, or the organization key, that accepted. - `accepted_ip` (string) - `terminated_at` (string (date-time)) - `terminated_from` (string (date-time)): The first month no longer committed, for an ended commitment. - `terminated_by` (string) - `clawback_usd` (number): The early-termination charge. - `clawback_invoice_id` (string) - `clawback_invoice_url` (string (uri)) - `clawback_invoice_pdf_url` (string (uri)) - `clawback_invoiced_at` (string (date-time)) - `ending_notice_at` (string (date-time)): When the organization was told the term is nearly over. - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — The commitment could not be loaded (`COMMITMENT_UNAVAILABLE`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/commitment' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/accept-commitment.md # Enter a commitment `POST https://evohub.io/api/v1/billing/commitment` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `acceptCommitment`. Agrees to the published commitment terms: a volume of `ehu` per month for the term the terms set, starting on the first day of next month, billed at the commitment price whether or not it is used. This is a binding agreement; read `terms.body` from `GET /api/v1/billing/commitment` first. - `accepted` must be `true`, and `terms_version` must be the version of the terms you read; if the terms have changed since, the request is refused with **409** `TERMS_CHANGED`. - `ehu` must be above zero. - With a commitment already running, only a larger `ehu` is accepted (**409** `NOT_AN_INCREASE` otherwise). The running one then ends without any early-termination charge and the new one starts next month. To commit to less, end the current commitment first. The acceptance records the terms version, who accepted (the person behind a personal key, or the organization key) and the address the request came from. Answers with the new commitment state. Requires the `billing:commitment:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `ehu` (number, required): EHU committed per month. - `terms_version` (integer, required): The `terms.version` you read and agree to. - `accepted` (boolean, required): Must be `true`. ## Responses ### 200 — The commitment was recorded. Content type: `application/json` Type: `object` - `data` (CommitmentState) - `terms` (CommitmentTerms) - `version` (integer) - `body` (string): The full text of the agreement. - `body_sha256` (string) - `term_months` (integer) - `published_at` (string (date-time)) - `active` (Commitment) - `id` (integer) - `org_id` (string) - `ehu_per_month` (number) - `starts_on` (string (date-time)): The first day of the first committed month. - `ends_on` (string (date-time)): The first day after the term (exclusive). - `status` (string, one of `active`, `completed`, `terminated`) - `terms_version` (integer) - `terms_sha256` (string): The hash of the agreement text that was accepted. - `accepted_at` (string (date-time)) - `accepted_by` (string): The person, or the organization key, that accepted. - `accepted_ip` (string) - `terminated_at` (string (date-time)) - `terminated_from` (string (date-time)): The first month no longer committed, for an ended commitment. - `terminated_by` (string) - `clawback_usd` (number): The early-termination charge. - `clawback_invoice_id` (string) - `clawback_invoice_url` (string (uri)) - `clawback_invoice_pdf_url` (string (uri)) - `clawback_invoiced_at` (string (date-time)) - `ending_notice_at` (string (date-time)): When the organization was told the term is nearly over. - `created_at` (string (date-time)) - `starts_on` (string (date-time)): When a commitment agreed now would start. - `estimated_clawback_usd` (number): What ending the active commitment today would cost; 0 without one. - `commitment_usd_per_ehu` (number) - `on_demand_usd_per_ehu` (number) - `history` (array of Commitment): The last 24 commitments. - `id` (integer) - `org_id` (string) - `ehu_per_month` (number) - `starts_on` (string (date-time)): The first day of the first committed month. - `ends_on` (string (date-time)): The first day after the term (exclusive). - `status` (string, one of `active`, `completed`, `terminated`) - `terms_version` (integer) - `terms_sha256` (string): The hash of the agreement text that was accepted. - `accepted_at` (string (date-time)) - `accepted_by` (string): The person, or the organization key, that accepted. - `accepted_ip` (string) - `terminated_at` (string (date-time)) - `terminated_from` (string (date-time)): The first month no longer committed, for an ended commitment. - `terminated_by` (string) - `clawback_usd` (number): The early-termination charge. - `clawback_invoice_id` (string) - `clawback_invoice_url` (string (uri)) - `clawback_invoice_pdf_url` (string (uri)) - `clawback_invoiced_at` (string (date-time)) - `ending_notice_at` (string (date-time)): When the organization was told the term is nearly over. - `created_at` (string (date-time)) ### 400 — The body is not JSON (`INVALID_BODY`), `accepted` is not `true` (`TERMS_NOT_ACCEPTED`), or `ehu` is not above zero (`INVALID_AMOUNT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 409 — Commitments are not being offered (`TERMS_UNAVAILABLE`), the terms changed since the version you sent (`TERMS_CHANGED`), the new volume is not larger than the running one (`NOT_AN_INCREASE`), or a commitment is already running (`COMMITMENT_EXISTS`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — The commitment could not be recorded (`COMMITMENT_FAILED`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/billing/commitment' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "ehu": 5000, "accepted": true, "terms_version": 1 }' ``` --- Source: https://docs.evohub.io/billing/end-commitment.md # End the commitment early `DELETE https://evohub.io/api/v1/billing/commitment` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `endCommitment`. Ends the running commitment before its term is up. It stops at the end of the current month: the month in progress stays committed and is billed on its terms. Ending early costs the discount already taken: every committed month served, including the current one, is repriced at the on-demand price, and the difference is owed (never less than zero). The amount is `estimated_clawback_usd` on `GET /api/v1/billing/commitment`, and is recorded as `clawback_usd` on the ended commitment. It is invoiced and charged to the card on file shortly afterwards, and then appears in payments as `commitment_clawback`; an amount below USD 0.50 is not charged. No request body. Answers with the new commitment state. Requires the `billing:commitment:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The commitment was ended. Content type: `application/json` Type: `object` - `data` (CommitmentState) - `terms` (CommitmentTerms) - `version` (integer) - `body` (string): The full text of the agreement. - `body_sha256` (string) - `term_months` (integer) - `published_at` (string (date-time)) - `active` (Commitment) - `id` (integer) - `org_id` (string) - `ehu_per_month` (number) - `starts_on` (string (date-time)): The first day of the first committed month. - `ends_on` (string (date-time)): The first day after the term (exclusive). - `status` (string, one of `active`, `completed`, `terminated`) - `terms_version` (integer) - `terms_sha256` (string): The hash of the agreement text that was accepted. - `accepted_at` (string (date-time)) - `accepted_by` (string): The person, or the organization key, that accepted. - `accepted_ip` (string) - `terminated_at` (string (date-time)) - `terminated_from` (string (date-time)): The first month no longer committed, for an ended commitment. - `terminated_by` (string) - `clawback_usd` (number): The early-termination charge. - `clawback_invoice_id` (string) - `clawback_invoice_url` (string (uri)) - `clawback_invoice_pdf_url` (string (uri)) - `clawback_invoiced_at` (string (date-time)) - `ending_notice_at` (string (date-time)): When the organization was told the term is nearly over. - `created_at` (string (date-time)) - `starts_on` (string (date-time)): When a commitment agreed now would start. - `estimated_clawback_usd` (number): What ending the active commitment today would cost; 0 without one. - `commitment_usd_per_ehu` (number) - `on_demand_usd_per_ehu` (number) - `history` (array of Commitment): The last 24 commitments. - `id` (integer) - `org_id` (string) - `ehu_per_month` (number) - `starts_on` (string (date-time)): The first day of the first committed month. - `ends_on` (string (date-time)): The first day after the term (exclusive). - `status` (string, one of `active`, `completed`, `terminated`) - `terms_version` (integer) - `terms_sha256` (string): The hash of the agreement text that was accepted. - `accepted_at` (string (date-time)) - `accepted_by` (string): The person, or the organization key, that accepted. - `accepted_ip` (string) - `terminated_at` (string (date-time)) - `terminated_from` (string (date-time)): The first month no longer committed, for an ended commitment. - `terminated_by` (string) - `clawback_usd` (number): The early-termination charge. - `clawback_invoice_id` (string) - `clawback_invoice_url` (string (uri)) - `clawback_invoice_pdf_url` (string (uri)) - `clawback_invoiced_at` (string (date-time)) - `ending_notice_at` (string (date-time)): When the organization was told the term is nearly over. - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 404 — No commitment is running (`NO_COMMITMENT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — The commitment could not be ended (`COMMITMENT_FAILED`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/billing/commitment' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/get-campaign-status.md # Get the organization's campaign standing `GET https://evohub.io/api/v1/billing/campaign` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `getCampaignStatus`. Where the organization stands with the credit campaigns running now. - `campaign` is the running sign-up campaign (credit for saving a first card), or `null`. `status` is the organization's standing with it: `eligible` (it will be paid when a card is saved), `granted`, `pending` (under review), `ineligible`, or `not_applicable` (no campaign, a card is already saved, or the organization signed up before the campaign began). `reason` is a short machine-readable explanation for `pending` and `ineligible`, otherwise `""`. - `integration` is the running first-integration campaign (credit for the first integration in a product), or `null`, and `gifts` the credit it — or an earlier one — paid the organization recently, newest first. Requires the `billing:usage:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The campaign standing. Content type: `application/json` Type: `object` - `data` (OrgCampaign) - `campaign` (PublicCampaign | null) - One of: - PublicCampaign - `id` (string) - `kind` (string, one of `card_signup`, `first_integration`) - `products` (array of string): The products a first-integration campaign pays for; `[]` for a sign-up campaign. - `name` (string) - `banner_text` (string) - `banner_link` (string) - `ehu` (number): The credit paid, in EHU. - `ends_at` (string (date-time)) - null - `status` (string, one of `eligible`, `granted`, `pending`, `ineligible`, `not_applicable`) - `reason` (string) - `integration` (PublicCampaign | null) - One of: - PublicCampaign - `id` (string) - `kind` (string, one of `card_signup`, `first_integration`) - `products` (array of string): The products a first-integration campaign pays for; `[]` for a sign-up campaign. - `name` (string) - `banner_text` (string) - `banner_link` (string) - `ehu` (number): The credit paid, in EHU. - `ends_at` (string (date-time)) - null - `gifts` (array of object) - `id` (string) - `product` (string) - `ehu` (number) - `granted_at` (string (date-time) | null) - `expires_at` (string (date-time) | null) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — The campaign could not be loaded (`CAMPAIGN_UNAVAILABLE`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/billing/campaign' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/billing/get-active-campaign.md # Get the running campaign `GET https://evohub.io/api/v1/campaigns/active` Part of the [Billing API](https://docs.evohub.io/billing.md) reference · operationId `getActiveCampaign`. The credit campaign running now — the sign-up campaign when there is one, otherwise the first-integration campaign — as the banner on the public site shows it. `data` is `null` when no campaign is running. No API key is needed. Answers may be cached for a minute. ## Responses ### 200 — The running campaign, or `null`. Headers: - `Cache-Control` (string): `public, max-age=60`. Content type: `application/json` Type: `object` - `data` (PublicCampaign | null) - One of: - PublicCampaign - `id` (string) - `kind` (string, one of `card_signup`, `first_integration`) - `products` (array of string): The products a first-integration campaign pays for; `[]` for a sign-up campaign. - `name` (string) - `banner_text` (string) - `banner_link` (string) - `ehu` (number): The credit paid, in EHU. - `ends_at` (string (date-time)) - null ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ### 500 — The campaign could not be loaded (`CAMPAIGN_UNAVAILABLE`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `STATEMENT_NOT_FOUND`) - `message` (string, required, example `No statement for that period.`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/campaigns/active' ``` --- Source: https://docs.evohub.io/board.md # Board API Version 1.0 · OpenAPI 3.1.0 Manage EvoHub Board from scripts and CI: create boards from a template, add lists and cards, move work across the board, assign people, set due dates, labels and checklists, comment, link cards to things in other EvoHub products, and drive the review gate that signs work off before it counts as done. **Authentication.** Every endpoint takes an EvoHub API key, sent as `Authorization: Bearer evohub_…` or as `X-API-Key: evohub_…`. Each operation names the scope the key needs in its description and in `x-evohub-permission`. A missing or invalid key gets **401**; a valid key without the scope gets **403**, never 401. A key holds exactly its scopes and never counts as an organization administrator. See the [API overview](/api-overview), [API keys and scopes](/api-keys-and-scopes) and [Errors](/errors). **Who a key acts as.** A personal key acts as the person who created it, with that person's teams. An organization key acts as itself: comments, approvals and the activity trail name the key, and it sees the world as the team it was configured with, if any. **Visibility.** A board is `private` (only the people named on it), `team` (everyone in the board's own team, `team_id`) or `org` (everyone in the organization). Anyone named on a board sees it whatever its visibility, and so does every member of a team the board was given to. Whoever creates a board is named on it. Visibility decides only who sees a board; what a caller may do on it is the scope. A board, list, card, label, checklist item, comment or link the caller cannot see answers **404** `NOT_FOUND`, exactly like one that does not exist. **Responses.** A successful answer carries its result in `data`. The analytics endpoint also sends `"success": true`. Errors carry an `error` object with a machine-readable `code`. Timestamps are RFC 3339 in UTC. Request bodies are JSON and may not contain fields the endpoint does not know (`INVALID_BODY`). **Placement.** Lists, cards and checklist items are placed by naming the neighbours they land between (`after`, `before`), or an end of the list (`to`: `top` or `bottom`), never by index. If the named neighbours are no longer next to each other the request is refused with **409** `STALE_PLACEMENT`: read the board again and retry. **Review gate.** A board can mark one list as its review list and one as its done list. A card moved into the review list waits there until someone approves or rejects it; nobody assigned to the card may approve it. On a board with a review list, only an approved card can enter the done list. ## Servers - `https://evohub.io` ## Authentication - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Boards Create, read, change and archive boards, and the templates a board starts from. - [GET /api/v1/board/templates](https://docs.evohub.io/board/list-board-templates.md): List board templates - [GET /api/v1/board/boards](https://docs.evohub.io/board/list-boards.md): List boards - [POST /api/v1/board/boards](https://docs.evohub.io/board/create-board.md): Create a board - [GET /api/v1/board/boards/{boardID}](https://docs.evohub.io/board/get-board.md): Get a board - [DELETE /api/v1/board/boards/{boardID}](https://docs.evohub.io/board/archive-board.md): Archive a board - [PATCH /api/v1/board/boards/{boardID}](https://docs.evohub.io/board/update-board.md): Update a board ## Sharing The people and teams named on a board, who see it whatever its visibility. - [GET /api/v1/board/boards/{boardID}/members](https://docs.evohub.io/board/list-board-members.md): List who a board is shared with - [POST /api/v1/board/boards/{boardID}/members](https://docs.evohub.io/board/add-board-member.md): Name a person on a board - [DELETE /api/v1/board/boards/{boardID}/members/{userID}](https://docs.evohub.io/board/remove-board-member.md): Take a person off a board - [PATCH /api/v1/board/boards/{boardID}/members/{userID}](https://docs.evohub.io/board/update-board-member.md): Update a board member (no effect) - [POST /api/v1/board/boards/{boardID}/teams](https://docs.evohub.io/board/share-board-with-team.md): Give a board to a team - [DELETE /api/v1/board/boards/{boardID}/teams/{teamID}](https://docs.evohub.io/board/unshare-board-from-team.md): Take a board back from a team ## Lists The columns of a board, and the part each plays in review. - [GET /api/v1/board/boards/{boardID}/lists](https://docs.evohub.io/board/get-board-content.md): List a board's lists and cards - [POST /api/v1/board/boards/{boardID}/lists](https://docs.evohub.io/board/create-list.md): Create a list - [DELETE /api/v1/board/lists/{listID}](https://docs.evohub.io/board/archive-list.md): Archive a list - [PATCH /api/v1/board/lists/{listID}](https://docs.evohub.io/board/rename-list.md): Rename a list - [POST /api/v1/board/lists/{listID}/move](https://docs.evohub.io/board/move-list.md): Move a list - [PUT /api/v1/board/lists/{listID}/flags](https://docs.evohub.io/board/set-list-flags.md): Set a list's review and done flags - [PUT /api/v1/board/lists/{listID}/celebration](https://docs.evohub.io/board/set-list-celebration.md): Set a list's celebration ## Cards Create, read, edit, move and archive cards. - [GET /api/v1/board/lists/{listID}/cards](https://docs.evohub.io/board/list-cards.md): List a list's cards - [POST /api/v1/board/lists/{listID}/cards](https://docs.evohub.io/board/create-card.md): Create a card - [GET /api/v1/board/cards/{cardID}](https://docs.evohub.io/board/get-card.md): Get a card - [DELETE /api/v1/board/cards/{cardID}](https://docs.evohub.io/board/archive-card.md): Archive a card - [PATCH /api/v1/board/cards/{cardID}](https://docs.evohub.io/board/update-card.md): Edit a card's title or description - [PUT /api/v1/board/cards/{cardID}/color](https://docs.evohub.io/board/set-card-color.md): Set a card's colour - [POST /api/v1/board/cards/{cardID}/move](https://docs.evohub.io/board/move-card.md): Move a card ## Card details Assignees, due dates, labels, checklists and comments. - [GET /api/v1/board/boards/{boardID}/labels](https://docs.evohub.io/board/list-labels.md): List a board's labels - [POST /api/v1/board/boards/{boardID}/labels](https://docs.evohub.io/board/create-label.md): Create a label - [DELETE /api/v1/board/boards/{boardID}/labels/{labelID}](https://docs.evohub.io/board/delete-label.md): Delete a label - [GET /api/v1/board/cards/{cardID}/detail](https://docs.evohub.io/board/get-card-detail.md): Get a card's details - [POST /api/v1/board/cards/{cardID}/assignees](https://docs.evohub.io/board/assign-card.md): Assign a person to a card - [DELETE /api/v1/board/cards/{cardID}/assignees/{userID}](https://docs.evohub.io/board/unassign-card.md): Take a person off a card - [PUT /api/v1/board/cards/{cardID}/due](https://docs.evohub.io/board/set-card-due.md): Set a card's due date - [PUT /api/v1/board/cards/{cardID}/labels/{labelID}](https://docs.evohub.io/board/add-card-label.md): Label a card - [DELETE /api/v1/board/cards/{cardID}/labels/{labelID}](https://docs.evohub.io/board/remove-card-label.md): Remove a label from a card - [POST /api/v1/board/cards/{cardID}/checklist](https://docs.evohub.io/board/add-checklist-item.md): Add a checklist item - [POST /api/v1/board/cards/{cardID}/comments](https://docs.evohub.io/board/add-comment.md): Comment on a card - [DELETE /api/v1/board/checklist-items/{itemID}](https://docs.evohub.io/board/delete-checklist-item.md): Delete a checklist item - [PATCH /api/v1/board/checklist-items/{itemID}](https://docs.evohub.io/board/update-checklist-item.md): Update a checklist item - [DELETE /api/v1/board/comments/{commentID}](https://docs.evohub.io/board/delete-comment.md): Delete a comment - [PATCH /api/v1/board/comments/{commentID}](https://docs.evohub.io/board/edit-comment.md): Edit a comment ## Links Links from a card to things in other EvoHub products. - [GET /api/v1/board/cards/{cardID}/links](https://docs.evohub.io/board/list-card-links.md): List a card's links - [POST /api/v1/board/cards/{cardID}/links](https://docs.evohub.io/board/add-card-link.md): Link a card to something - [DELETE /api/v1/board/links/{linkID}](https://docs.evohub.io/board/remove-card-link.md): Remove a link ## Review Approve or reject cards waiting for review, and the cards waiting for you. - [GET /api/v1/board/review-queue](https://docs.evohub.io/board/list-review-queue.md): List cards waiting for your review - [POST /api/v1/board/cards/{cardID}/approve](https://docs.evohub.io/board/approve-card.md): Approve a card - [POST /api/v1/board/cards/{cardID}/reject](https://docs.evohub.io/board/reject-card.md): Reject a card ## Archive Cards that have left a board, and putting them back. - [GET /api/v1/board/boards/{boardID}/archive](https://docs.evohub.io/board/list-archived-cards.md): List a board's archived cards - [GET /api/v1/board/archive/cards/{cardID}](https://docs.evohub.io/board/get-archived-card.md): Get an archived card - [POST /api/v1/board/archive/cards/{cardID}/restore](https://docs.evohub.io/board/restore-archived-card.md): Restore an archived card ## Activity What happened on a card, and on every board you can see. - [GET /api/v1/board/activity](https://docs.evohub.io/board/list-board-activity.md): List activity across boards - [GET /api/v1/board/cards/{cardID}/history](https://docs.evohub.io/board/get-card-history.md): Get a card's history ## Analytics Throughput, cycle time, work in progress and review figures across your boards. - [GET /api/v1/board/analytics](https://docs.evohub.io/board/get-board-analytics.md): Get board analytics --- Source: https://docs.evohub.io/board/list-board-templates.md # List board templates `GET https://evohub.io/api/v1/board/templates` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `listBoardTemplates`. The templates a new board can start from, in the order they are offered. Pass a template's `id` as `template` when creating a board. Each list a template creates says whether it is the board's review list or done list. Requires the `board:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The templates. Content type: `application/json` Type: `object` - `data` (object) - `templates` (array of BoardTemplate) - `id` (string, example `kanban-review`) - `name` (string) - `description` (string) - `lists` (array of TemplateList | null): The lists the template creates, in order. `null` for the blank template. - `name` (string) - `is_review_list` (boolean) - `is_done_list` (boolean) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/templates' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/list-boards.md # List boards `GET https://evohub.io/api/v1/board/boards` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `listBoards`. Every board the caller can see, newest first. Archived boards are not listed. The list carries `auto_archive_days` as `0`; read a single board for its setting. Requires the `board:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The boards. Content type: `application/json` Type: `object` - `data` (object) - `boards` (array of Board) - `id` (string, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`) - `name` (string, max length 120) - `description` (string, max length 500) - `visibility` (Visibility, one of `private`, `team`, `org`) - `team_id` (string): The team a `team` board belongs to; `""` for every other board. - `card_prefix` (string, example `PLT`): The front of every card reference on the board, as in `PLT-42`. Never changes. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `auto_archive_days` (integer, min 1, max 365): Days a card may sit in the done list before it is archived. `0` in the board list. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/boards' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/create-board.md # Create a board `POST https://evohub.io/api/v1/board/boards` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `createBoard`. Creates a board and names the caller on it. `name` is required. `visibility` defaults to `private`. A `team` board must carry the `team_id` it belongs to, which must be a team the caller is in; any other visibility must not carry one. `template` is a template `id` from *List board templates*; left out, the board starts with no lists. `card_prefix` is the front of every card reference on the board (`PLT` in `PLT-42`): 2 to 6 letters, upper-cased for you, and it can never be changed afterwards. Left out, one is derived from the name and stepped until it is free. A prefix you choose that another board already uses is refused, naming that board. A new board archives cards that have sat in its done list for 365 days; change that with `auto_archive_days` on update. Requires the `board:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `CreateBoardRequest` - `name` (string, required, max length 120) - `description` (string, max length 500) - `visibility` (Visibility, one of `private`, `team`, `org`) - `team_id` (string): Required with `visibility` `team`, refused with any other. - `template` (string, example `kanban-review`): A template ID; empty or left out is `blank`. - `card_prefix` (string, example `PLT`): 2 to 6 letters. Left out, one is derived from the name. - `auto_archive_days` (integer): Ignored on create; a new board starts at 365. ## Responses ### 201 — The board was created. Content type: `application/json` Type: `BoardEnvelope` - `data` (object) - `board` (Board) - `id` (string, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`) - `name` (string, max length 120) - `description` (string, max length 500) - `visibility` (Visibility, one of `private`, `team`, `org`) - `team_id` (string): The team a `team` board belongs to; `""` for every other board. - `card_prefix` (string, example `PLT`): The front of every card reference on the board, as in `PLT-42`. Never changes. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `auto_archive_days` (integer, min 1, max 365): Days a card may sit in the done list before it is archived. `0` in the board list. ### 400 — The body is not JSON for this endpoint (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which): a missing or over-long `name` (120 characters), a `description` over 500 characters, an unknown `visibility`, a `team` board without `team_id` or another board with one, an unknown `template`, or a `card_prefix` that is not 2 to 6 letters or is already used. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks `board:board:write`, or `team_id` names a team the caller is not in (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — Another board took the chosen `card_prefix` at the same moment (`PREFIX_TAKEN`). Choose another. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/boards' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Platform", "template": "kanban-review", "visibility": "org", "card_prefix": "PLT", "description": "Infrastructure and platform work." }' ``` --- Source: https://docs.evohub.io/board/get-board.md # Get a board `GET https://evohub.io/api/v1/board/boards/{boardID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `getBoard`. One board and the people named on it. Its lists and cards are read with *List a board's lists and cards*. Requires the `board:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. ## Responses ### 200 — The board. Content type: `application/json` Type: `object` - `data` (object) - `board` (Board) - `id` (string, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`) - `name` (string, max length 120) - `description` (string, max length 500) - `visibility` (Visibility, one of `private`, `team`, `org`) - `team_id` (string): The team a `team` board belongs to; `""` for every other board. - `card_prefix` (string, example `PLT`): The front of every card reference on the board, as in `PLT-42`. Never changes. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `auto_archive_days` (integer, min 1, max 365): Days a card may sit in the done list before it is archived. `0` in the board list. - `members` (array of Member) - `user_id` (string) - `added_by` (string) - `added_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/archive-board.md # Archive a board `DELETE https://evohub.io/api/v1/board/boards/{boardID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `archiveBoard`. Archives the board: it disappears from every list and stops answering, but nothing on it is deleted. Requires the `board:board:delete` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. ## Responses ### 204 — The board was archived. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/update-board.md # Update a board `PATCH https://evohub.io/api/v1/board/boards/{boardID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `updateBoard`. Changes a board's name, description, visibility, team and auto-archive setting. Send `name` and `description` every time: `name` is required, and a missing `description` is saved as empty. Leave out `visibility` to keep it, and with it the board's team. When you send `visibility: team`, send the `team_id` too; moving a board into a team needs the caller to be in that team. `auto_archive_days` (1–365) is how long a card may sit in the done list before it is archived; left out, it is not changed. `template` and `card_prefix` are ignored here. Requires the `board:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. ## Request body (required) Content type: `application/json` Type: `UpdateBoardRequest` - `name` (string, required, max length 120) - `description` (string, max length 500): Saved as empty when left out. - `visibility` (Visibility, one of `private`, `team`, `org`) - `team_id` (string) - `auto_archive_days` (integer, min 1, max 365) - `template` (string): Ignored. - `card_prefix` (string): Ignored; a board's prefix never changes. ## Responses ### 200 — The board after the change. Content type: `application/json` Type: `BoardEnvelope` - `data` (object) - `board` (Board) - `id` (string, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`) - `name` (string, max length 120) - `description` (string, max length 500) - `visibility` (Visibility, one of `private`, `team`, `org`) - `team_id` (string): The team a `team` board belongs to; `""` for every other board. - `card_prefix` (string, example `PLT`): The front of every card reference on the board, as in `PLT-42`. Never changes. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `auto_archive_days` (integer, min 1, max 365): Days a card may sit in the done list before it is archived. `0` in the board list. ### 400 — The body is not JSON for this endpoint (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`): the name, description, visibility and team rules of creating a board, or `auto_archive_days` outside 1–365. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks `board:board:write`, or the board is being moved into a team the caller is not in (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Platform", "team_id": "team_8b2c4d6e-1f3a-4b5c-9d7e-0a1b2c3d4e5f", "visibility": "team", "description": "Infrastructure and platform work.", "auto_archive_days": 90 }' ``` --- Source: https://docs.evohub.io/board/list-board-members.md # List who a board is shared with `GET https://evohub.io/api/v1/board/boards/{boardID}/members` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `listBoardMembers`. The people named on the board and the teams it was given to. Requires the `board:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. ## Responses ### 200 — The people and teams. Content type: `application/json` Type: `object` - `data` (object) - `members` (array of Member) - `user_id` (string) - `added_by` (string) - `added_at` (string (date-time)) - `teams` (array of TeamShare) - `board_id` (string) - `org_id` (string) - `team_id` (string) - `added_by` (string) - `added_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/members' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/add-board-member.md # Name a person on a board `POST https://evohub.io/api/v1/board/boards/{boardID}/members` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `addBoardMember`. Names a person on the board, so they see it whatever its visibility. The person must be a member of the organization. Naming someone who is already on the board changes nothing and answers the same. What they may do on the board comes from their organization role, not from the board: a `role` in the body is accepted and ignored. Requires the `board:member:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. ## Request body (required) Content type: `application/json` Type: `MemberRequest` - `user_id` (string) - `role` (string, deprecated): Ignored. Boards have no roles. ## Responses ### 201 — The person is on the board. Content type: `application/json` Type: `MemberEnvelope` - `data` (object) - `member` (Member) - `user_id` (string) - `added_by` (string) - `added_at` (string (date-time)) ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — The board is not one the caller can see (`NOT_FOUND`), or the person is not in this organization (`USER_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 503 — The person's membership of the organization could not be checked right now (`MEMBERSHIP_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/members' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "user_id": "usr_6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f" }' ``` --- Source: https://docs.evohub.io/board/remove-board-member.md # Take a person off a board `DELETE https://evohub.io/api/v1/board/boards/{boardID}/members/{userID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `removeBoardMember`. Takes the person off the board. They still see it if its visibility or one of their teams lets them. Requires the `board:member:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. - `userID` (string, required, example `usr_6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f`): The person's user ID. ## Responses ### 204 — The person is off the board. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — The board is not one the caller can see, or the person is not named on it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/members/usr_6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/update-board-member.md # Update a board member (no effect) `PATCH https://evohub.io/api/v1/board/boards/{boardID}/members/{userID}` (deprecated) Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `updateBoardMember`. Kept for clients written when boards had roles of their own. Boards no longer have roles, so this changes nothing: it answers the person's entry, or **404** if they are not named on the board. Requires the `board:member:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. - `userID` (string, required, example `usr_6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f`): The person's user ID. ## Request body (required) Content type: `application/json` Type: `MemberRequest` - `user_id` (string) - `role` (string, deprecated): Ignored. Boards have no roles. ## Responses ### 200 — The person's entry, unchanged. Content type: `application/json` Type: `MemberEnvelope` - `data` (object) - `member` (Member) - `user_id` (string) - `added_by` (string) - `added_at` (string (date-time)) ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/members/usr_6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "role": "editor" }' ``` --- Source: https://docs.evohub.io/board/share-board-with-team.md # Give a board to a team `POST https://evohub.io/api/v1/board/boards/{boardID}/teams` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `shareBoardWithTeam`. Gives the board to another team, whose members then see it. The board's own team already sees a `team` board and cannot be added. Giving it to a team that already has it changes nothing. A `role` in the body is accepted and ignored. Requires the `board:member:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. ## Request body (required) Content type: `application/json` Type: `object` - `team_id` (string, required) - `role` (string, deprecated): Ignored. ## Responses ### 201 — The team has the board. Content type: `application/json` Type: `object` - `data` (object) - `team` (TeamShare) - `board_id` (string) - `org_id` (string) - `team_id` (string) - `added_by` (string) - `added_at` (string (date-time)) ### 400 — The body is not JSON for this endpoint (`INVALID_BODY`), `team_id` is missing, or it is the board's own team (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/teams' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "team_id": "team_1c3e5a7b-2d4f-4a6c-8e0b-9f1a2b3c4d5e" }' ``` --- Source: https://docs.evohub.io/board/unshare-board-from-team.md # Take a board back from a team `DELETE https://evohub.io/api/v1/board/boards/{boardID}/teams/{teamID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `unshareBoardFromTeam`. Takes the board back from a team it was given to. Requires the `board:member:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. - `teamID` (string, required, example `team_1c3e5a7b-2d4f-4a6c-8e0b-9f1a2b3c4d5e`): The team's ID. ## Responses ### 204 — The team no longer has the board. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — The board is not one the caller can see, or it was not given to that team (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/teams/team_1c3e5a7b-2d4f-4a6c-8e0b-9f1a2b3c4d5e' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/get-board-content.md # List a board's lists and cards `GET https://evohub.io/api/v1/board/boards/{boardID}/lists` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `getBoardContent`. The board's lists in display order, each with the first page of its cards. Each list says how many cards it holds (`card_count`) and whether more exist (`has_more`); read further with *List a list's cards*. The filters below narrow which cards count as being in a list, the counts included. Requires the `board:list:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. ## Query parameters - `cards` (integer, default `50`, min 1, max 500): Cards per list. Default 50, at most 500. - `q` (string): Match card titles, case-insensitively. A reference such as `PLT-42`, or a bare number, also matches the card with that number. - `assignee` (string, value `me`): `me` keeps only cards the caller is assigned to. Any other value is ignored. - `label` (string): Keep only cards carrying this label ID. - `due` (string, value `none`): `none` keeps only cards with no due date. - `due_from` (string (date-time)): Keep only cards due at or after this RFC 3339 instant. - `due_to` (string (date-time)): Keep only cards due at or before this RFC 3339 instant. - `created_from` (string (date-time)): Keep only cards created at or after this RFC 3339 instant. - `created_to` (string (date-time)): Keep only cards created at or before this RFC 3339 instant. ## Responses ### 200 — The lists and their first cards. Content type: `application/json` Type: `object` - `data` (object) - `lists` (array of List) - `id` (string) - `board_id` (string) - `name` (string, max length 80) - `is_review_list` (boolean) - `is_done_list` (boolean) - `celebration` (Celebration, one of ``, `confetti`, `hearts`, `sparkles`, `fireworks`, `balloons`, `bubbles`, `petals`, `emoji`, `glow`) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `cards` (array of Card): The first page of the list's cards. - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. - `card_count` (integer): How many cards the list holds, under the filters. - `has_more` (boolean) ### 400 — A date filter is not an RFC 3339 timestamp (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/lists' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/create-list.md # Create a list `POST https://evohub.io/api/v1/board/boards/{boardID}/lists` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `createList`. Adds a list to the board. `name` is required (at most 80 characters). Without `placement` the list goes to the end. Requires the `board:list:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. ## Request body (required) Content type: `application/json` Type: `ListRequest` - `name` (string, required, max length 80) - `placement` (Placement) - `after` (string): The ID of the item it should follow. Empty means the top. - `before` (string): The ID of the item it should precede. Empty means the bottom. - `to` (string, one of `top`, `bottom`): An end of the list. Any other value is ignored. ## Responses ### 201 — The list was created. Content type: `application/json` Type: `object` - `data` (object) - `list` (List) - `id` (string) - `board_id` (string) - `name` (string, max length 80) - `is_review_list` (boolean) - `is_done_list` (boolean) - `celebration` (Celebration, one of ``, `confetti`, `hearts`, `sparkles`, `fireworks`, `balloons`, `bubbles`, `petals`, `emoji`, `glow`) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `cards` (array of Card): The first page of the list's cards. - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. - `card_count` (integer): How many cards the list holds, under the filters. - `has_more` (boolean) ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The neighbours named in `placement` are no longer next to each other (`STALE_PLACEMENT`). Read the board again and retry. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/lists' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Blocked", "placement": { "after": "lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7" } }' ``` --- Source: https://docs.evohub.io/board/archive-list.md # Archive a list `DELETE https://evohub.io/api/v1/board/lists/{listID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `archiveList`. Archives the list and every card in it. The cards go to the board's archive, from where they can be restored. Requires the `board:list:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `listID` (string, required, example `lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7`): The list's ID. ## Responses ### 204 — The list was archived. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/board/lists/lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/rename-list.md # Rename a list `PATCH https://evohub.io/api/v1/board/lists/{listID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `renameList`. Renames the list. `name` is required (at most 80 characters). `placement` is accepted and ignored; move a list with *Move a list*. Requires the `board:list:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `listID` (string, required, example `lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7`): The list's ID. ## Request body (required) Content type: `application/json` Type: `ListRequest` - `name` (string, required, max length 80) - `placement` (Placement) - `after` (string): The ID of the item it should follow. Empty means the top. - `before` (string): The ID of the item it should precede. Empty means the bottom. - `to` (string, one of `top`, `bottom`): An end of the list. Any other value is ignored. ## Responses ### 200 — The new name. Content type: `application/json` Type: `object` - `data` (object) - `name` (string) ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/board/lists/lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Waiting on others" }' ``` --- Source: https://docs.evohub.io/board/move-list.md # Move a list `POST https://evohub.io/api/v1/board/lists/{listID}/move` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `moveList`. Moves the list to a new place on its board, between the neighbours named in `placement`. Requires the `board:list:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `listID` (string, required, example `lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7`): The list's ID. ## Request body (required) Content type: `application/json` Type: `object` - `placement` (Placement) - `after` (string): The ID of the item it should follow. Empty means the top. - `before` (string): The ID of the item it should precede. Empty means the bottom. - `to` (string, one of `top`, `bottom`): An end of the list. Any other value is ignored. ## Responses ### 204 — The list was moved. ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The neighbours named in `placement` are no longer next to each other (`STALE_PLACEMENT`). Read the board again and retry. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/lists/lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7/move' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "placement": { "to": "top" } }' ``` --- Source: https://docs.evohub.io/board/set-list-flags.md # Set a list's review and done flags `PUT https://evohub.io/api/v1/board/lists/{listID}/flags` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `setListFlags`. Marks the list as the board's review list, its done list, or neither. A list cannot be both. Cards that sit in a done list are archived after the board's `auto_archive_days`. Changing which lists take part in review is how the board is run, so it needs `board:board:write` as well as the scope below; without it the answer is **403** `FORBIDDEN`. Requires the `board:review:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `listID` (string, required, example `lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7`): The list's ID. ## Request body (required) Content type: `application/json` Type: `ListFlags` - `is_review_list` (boolean): The board's review list. - `is_done_list` (boolean): The board's done list. ## Responses ### 200 — The list's flags. Content type: `application/json` Type: `object` - `data` (object) - `flags` (ListFlags) - `is_review_list` (boolean): The board's review list. - `is_done_list` (boolean): The board's done list. ### 400 — The body is not JSON for this endpoint (`INVALID_BODY`), or both flags are true (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks `board:review:write` or `board:board:write` (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/board/lists/lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7/flags' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "is_done_list": false, "is_review_list": true }' ``` --- Source: https://docs.evohub.io/board/set-list-celebration.md # Set a list's celebration `PUT https://evohub.io/api/v1/board/lists/{listID}/celebration` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `setListCelebration`. The effect the board plays when a card lands in this list, or `""` for none. One of `confetti`, `hearts`, `sparkles`, `fireworks`, `balloons`, `bubbles`, `petals`, `emoji`, `glow`. Requires the `board:list:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `listID` (string, required, example `lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7`): The list's ID. ## Request body (required) Content type: `application/json` Type: `object` - `celebration` (Celebration, one of ``, `confetti`, `hearts`, `sparkles`, `fireworks`, `balloons`, `bubbles`, `petals`, `emoji`, `glow`) ## Responses ### 200 — The list's celebration. Content type: `application/json` Type: `object` - `data` (object) - `celebration` (Celebration, one of ``, `confetti`, `hearts`, `sparkles`, `fireworks`, `balloons`, `bubbles`, `petals`, `emoji`, `glow`) ### 400 — The body is not JSON for this endpoint (`INVALID_BODY`), or the effect is unknown (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/board/lists/lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7/celebration' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "celebration": "confetti" }' ``` --- Source: https://docs.evohub.io/board/list-cards.md # List a list's cards `GET https://evohub.io/api/v1/board/lists/{listID}/cards` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `listCards`. A page of one list's cards, in display order. Without `after` the first page comes back; pass the last card's `id` as `after` to continue while `has_more` is true. `card_count` is how many cards the list holds under the same filters. Requires the `board:card:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `listID` (string, required, example `lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7`): The list's ID. ## Query parameters - `after` (string): Continue after this card ID. - `limit` (integer, default `50`, min 1, max 500): Cards per page. Default 50, at most 500. - `q` (string): Match card titles, case-insensitively. A reference such as `PLT-42`, or a bare number, also matches the card with that number. - `assignee` (string, value `me`): `me` keeps only cards the caller is assigned to. Any other value is ignored. - `label` (string): Keep only cards carrying this label ID. - `due` (string, value `none`): `none` keeps only cards with no due date. - `due_from` (string (date-time)): Keep only cards due at or after this RFC 3339 instant. - `due_to` (string (date-time)): Keep only cards due at or before this RFC 3339 instant. - `created_from` (string (date-time)): Keep only cards created at or after this RFC 3339 instant. - `created_to` (string (date-time)): Keep only cards created at or before this RFC 3339 instant. ## Responses ### 200 — A page of cards. Content type: `application/json` Type: `object` - `data` (object) - `cards` (array of Card) - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `state` (string, one of `pending`, `approved`) - `approved_by` (string) - `approved_at` (string (date-time)) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. - `card_count` (integer) - `has_more` (boolean) ### 400 — A date filter is not an RFC 3339 timestamp (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/lists/lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7/cards' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/create-card.md # Create a card `POST https://evohub.io/api/v1/board/lists/{listID}/cards` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `createCard`. Adds a card to the list. `title` is required (at most 200 characters); `description` may be up to 20,000 characters. Without `placement` the card goes to the bottom. The card gets the next `number` on its board, which with the board's prefix is its reference (`PLT-119`). `list_id` in the body is ignored: the list is the one in the path. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `listID` (string, required, example `lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7`): The list's ID. ## Request body (required) Content type: `application/json` Type: `object` - `title` (string, required, max length 200) - `description` (string, max length 20000) - `list_id` (string): Ignored. - `placement` (Placement) - `after` (string): The ID of the item it should follow. Empty means the top. - `before` (string): The ID of the item it should precede. Empty means the bottom. - `to` (string, one of `top`, `bottom`): An end of the list. Any other value is ignored. ## Responses ### 201 — The card was created. Content type: `application/json` Type: `CardEnvelope` - `data` (object) - `card` (Card) - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `state` (string, one of `pending`, `approved`) - `approved_by` (string) - `approved_at` (string (date-time)) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The neighbours named in `placement` are no longer next to each other (`STALE_PLACEMENT`). Read the board again and retry. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/lists/lst_2b3c4d5e6f7a48b9c0d1e2f3a4b5c6d7/cards' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "title": "Renew the TLS certificate for the API", "placement": { "to": "top" }, "description": "Expires on 2026-12-05." }' ``` --- Source: https://docs.evohub.io/board/get-card.md # Get a card `GET https://evohub.io/api/v1/board/cards/{cardID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `getCard`. One card. Its assignees, labels, checklist, comments and links come with *Get a card's details*. An archived card answers **404** here; read it from the archive. Requires the `board:card:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Responses ### 200 — The card. Content type: `application/json` Type: `CardEnvelope` - `data` (object) - `card` (Card) - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `state` (string, one of `pending`, `approved`) - `approved_by` (string) - `approved_at` (string (date-time)) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/archive-card.md # Archive a card `DELETE https://evohub.io/api/v1/board/cards/{cardID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `archiveCard`. Moves the card to the board's archive, from where it can be restored. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Responses ### 204 — The card was archived. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/update-card.md # Edit a card's title or description `PATCH https://evohub.io/api/v1/board/cards/{cardID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `updateCard`. Changes the card's `title`, `description` or both; a field left out is not changed, and at least one must be sent. Every edit raises the card's `version` by one. Send the `version` you last read to refuse the edit if someone changed the card since (**409** `EDIT_CONFLICT`); leave it out to apply the edit regardless. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Request body (required) Content type: `application/json` Type: `object` - `title` (string, max length 200) - `description` (string, max length 20000) - `version` (integer): The version this edit was made against. ## Responses ### 200 — The card after the edit. Content type: `application/json` Type: `CardEnvelope` - `data` (object) - `card` (Card) - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `state` (string, one of `pending`, `approved`) - `approved_by` (string) - `approved_at` (string (date-time)) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. ### 400 — The body is not JSON for this endpoint (`INVALID_BODY`), or (`VALIDATION_ERROR`) neither field was sent, the title is blank or over 200 characters, or the description is over 20,000. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The card changed since `version` (`EDIT_CONFLICT`). Read it again and reapply the edit. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "version": 3, "description": "Rotate before the old ones expire on the 30th. Staging first." }' ``` --- Source: https://docs.evohub.io/board/set-card-color.md # Set a card's colour `PUT https://evohub.io/api/v1/board/cards/{cardID}/color` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `setCardColor`. Sets the card's colour, a hex colour such as `#1d4ed8`, or `""` for none. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Request body (required) Content type: `application/json` Type: `object` - `color` (string): A hex colour, or empty for none. ## Responses ### 200 — The card with its new colour. Content type: `application/json` Type: `CardEnvelope` - `data` (object) - `card` (Card) - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `state` (string, one of `pending`, `approved`) - `approved_by` (string) - `approved_at` (string (date-time)) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/color' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "color": "#1d4ed8" }' ``` --- Source: https://docs.evohub.io/board/move-card.md # Move a card `POST https://evohub.io/api/v1/board/cards/{cardID}/move` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `moveCard`. Moves the card into `list_id` (its own list, to reorder it) between the neighbours named in `placement`. The list must be on the card's board. The review gate applies: | Move | Result | | --- | --- | | Into the review list | The card starts waiting for review. | | Out of the review list while waiting | Refused, **409** `REVIEW_PENDING`: approve or reject it. | | Into the done list, on a board with a review list | Only if approved; otherwise **409** `REVIEW_REQUIRED`. | | Anywhere else | Any approval the card had is dropped. | Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Request body (required) Content type: `application/json` Type: `object` - `list_id` (string, required) - `placement` (Placement) - `after` (string): The ID of the item it should follow. Empty means the top. - `before` (string): The ID of the item it should precede. Empty means the bottom. - `to` (string, one of `top`, `bottom`): An end of the list. Any other value is ignored. ## Responses ### 204 — The card was moved. ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — The card is not one the caller can see, or `list_id` is not a list on the card's board (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The review gate refused the move (`REVIEW_PENDING`, `REVIEW_REQUIRED`), or the named neighbours have moved (`STALE_PLACEMENT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/move' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "list_id": "lst_3c4d5e6f7a8b49c0d1e2f3a4b5c6d7e8", "placement": { "to": "bottom" } }' ``` --- Source: https://docs.evohub.io/board/list-labels.md # List a board's labels `GET https://evohub.io/api/v1/board/boards/{boardID}/labels` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `listLabels`. The labels defined on the board. Requires the `board:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. ## Responses ### 200 — The labels. Content type: `application/json` Type: `object` - `data` (object) - `labels` (array of Label) - `id` (string) - `board_id` (string) - `name` (string, max length 40) - `color` (string, example `#b91c1c`) - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/labels' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/create-label.md # Create a label `POST https://evohub.io/api/v1/board/boards/{boardID}/labels` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `createLabel`. Defines a label on the board. `name` is required (at most 40 characters) and unique on the board; `color` is a hex colour such as `#1d4ed8`. Requires the `board:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. ## Request body (required) Content type: `application/json` Type: `object` - `name` (string, required, max length 40) - `color` (string, required, pattern `^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$`) ## Responses ### 201 — The label was created. Content type: `application/json` Type: `object` - `data` (object) - `label` (Label) - `id` (string) - `board_id` (string) - `name` (string, max length 40) - `color` (string, example `#b91c1c`) - `created_at` (string (date-time)) ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The board already has a label with this name (`LABEL_NAME_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/labels' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "security", "color": "#b91c1c" }' ``` --- Source: https://docs.evohub.io/board/delete-label.md # Delete a label `DELETE https://evohub.io/api/v1/board/boards/{boardID}/labels/{labelID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `deleteLabel`. Deletes the label from the board and from every card carrying it. Requires the `board:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. - `labelID` (string, required, example `lbl_0a1b2c3d4e5f46a7b8c9d0e1f2a3b4c5`): The label's ID. ## Responses ### 204 — The label was deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/labels/lbl_0a1b2c3d4e5f46a7b8c9d0e1f2a3b4c5' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/get-card-detail.md # Get a card's details `GET https://evohub.io/api/v1/board/cards/{cardID}/detail` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `getCardDetail`. The card with everything on it: assignees, labels, checklist, comments and links. Names and avatars are not included; look people up by `user_id`. Requires the `board:card:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Responses ### 200 — The card and its details. Content type: `application/json` Type: `object` - `data` (object) - `card` (CardDetail) - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `state` (string, one of `pending`, `approved`) - `approved_by` (string) - `approved_at` (string (date-time)) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. - `assignees` (array of Assignee) - `user_id` (string) - `assigned_by` (string) - `assigned_at` (string (date-time)) - `labels` (array of Label) - `id` (string) - `board_id` (string) - `name` (string, max length 40) - `color` (string, example `#b91c1c`) - `created_at` (string (date-time)) - `checklist` (array of ChecklistItem) - `id` (string) - `card_id` (string) - `text` (string, max length 500) - `done` (boolean) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `comments` (array of Comment) - `id` (string) - `card_id` (string) - `author_id` (string) - `body` (string, max length 10000) - `edited_at` (string (date-time)): Present once the comment has been edited. - `created_at` (string (date-time)) - `links` (array of Link) - `id` (string) - `card_id` (string) - `service` (string, example `oncall`) - `entity_type` (string, example `incident`) - `entity_id` (string) - `url` (string) - `title` (string) - `created_by` (string) - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/detail' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/assign-card.md # Assign a person to a card `POST https://evohub.io/api/v1/board/cards/{cardID}/assignees` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `assignCard`. Puts a person on the card. They must be a member of the organization. Assigning someone already on the card changes nothing. An assignee cannot approve the card in review. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Request body (required) Content type: `application/json` Type: `object` - `user_id` (string, required) ## Responses ### 204 — The person is on the card. ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — The card is not one the caller can see (`NOT_FOUND`), or the person is not in this organization (`USER_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 503 — The person's membership of the organization could not be checked right now (`MEMBERSHIP_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/assignees' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "user_id": "usr_6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f" }' ``` --- Source: https://docs.evohub.io/board/unassign-card.md # Take a person off a card `DELETE https://evohub.io/api/v1/board/cards/{cardID}/assignees/{userID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `unassignCard`. Takes the person off the card. Works for people who have since left the organization. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. - `userID` (string, required, example `usr_6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f`): The person's user ID. ## Responses ### 204 — The person is off the card. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — The card is not one the caller can see, or the person is not on it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/assignees/usr_6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/set-card-due.md # Set a card's due date `PUT https://evohub.io/api/v1/board/cards/{cardID}/due` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `setCardDue`. Sets the card's due date, an RFC 3339 timestamp. `null` clears it. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Request body (required) Content type: `application/json` Type: `object` - `due_at` (string (date-time) | null) ## Responses ### 204 — The due date was set. ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/due' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "due_at": "2026-11-28T17:00:00Z" }' ``` --- Source: https://docs.evohub.io/board/add-card-label.md # Label a card `PUT https://evohub.io/api/v1/board/cards/{cardID}/labels/{labelID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `addCardLabel`. Puts one of the board's labels on the card. Adding a label the card already carries changes nothing. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. - `labelID` (string, required, example `lbl_0a1b2c3d4e5f46a7b8c9d0e1f2a3b4c5`): The label's ID. ## Responses ### 204 — The card carries the label. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — The card is not one the caller can see, or the label is not on its board (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/labels/lbl_0a1b2c3d4e5f46a7b8c9d0e1f2a3b4c5' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/remove-card-label.md # Remove a label from a card `DELETE https://evohub.io/api/v1/board/cards/{cardID}/labels/{labelID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `removeCardLabel`. Takes the label off the card. The label stays on the board. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. - `labelID` (string, required, example `lbl_0a1b2c3d4e5f46a7b8c9d0e1f2a3b4c5`): The label's ID. ## Responses ### 204 — The label is off the card. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — The card is not one the caller can see, or it does not carry the label (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/labels/lbl_0a1b2c3d4e5f46a7b8c9d0e1f2a3b4c5' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/add-checklist-item.md # Add a checklist item `POST https://evohub.io/api/v1/board/cards/{cardID}/checklist` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `addChecklistItem`. Adds an item to the card's checklist. `text` is required (at most 500 characters). A new item is never done; `done` is ignored here. Without `placement` the item goes to the bottom. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Request body (required) Content type: `application/json` Type: `ChecklistRequest` - `text` (string, max length 500) - `done` (boolean) - `placement` (Placement) - `after` (string): The ID of the item it should follow. Empty means the top. - `before` (string): The ID of the item it should precede. Empty means the bottom. - `to` (string, one of `top`, `bottom`): An end of the list. Any other value is ignored. ## Responses ### 201 — The item was added. Content type: `application/json` Type: `object` - `data` (object) - `item` (ChecklistItem) - `id` (string) - `card_id` (string) - `text` (string, max length 500) - `done` (boolean) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The neighbours named in `placement` are no longer next to each other (`STALE_PLACEMENT`). Read the board again and retry. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/checklist' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "text": "Rotate on production" }' ``` --- Source: https://docs.evohub.io/board/add-comment.md # Comment on a card `POST https://evohub.io/api/v1/board/cards/{cardID}/comments` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `addComment`. Adds a comment to the card. `body` is required (at most 10,000 characters). Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Request body (required) Content type: `application/json` Type: `CommentRequest` - `body` (string, required, max length 10000) ## Responses ### 201 — The comment was added. Content type: `application/json` Type: `object` - `data` (object) - `comment` (Comment) - `id` (string) - `card_id` (string) - `author_id` (string) - `body` (string, max length 10000) - `edited_at` (string (date-time)): Present once the comment has been edited. - `created_at` (string (date-time)) ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/comments' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "Staging is done; production on Monday." }' ``` --- Source: https://docs.evohub.io/board/delete-checklist-item.md # Delete a checklist item `DELETE https://evohub.io/api/v1/board/checklist-items/{itemID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `deleteChecklistItem`. Deletes the item from its card's checklist. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `itemID` (string, required, example `chk_1b2c3d4e5f6a47b8c9d0e1f2a3b4c5d6`): The checklist item's ID. ## Responses ### 204 — The item was deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/board/checklist-items/chk_1b2c3d4e5f6a47b8c9d0e1f2a3b4c5d6' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/update-checklist-item.md # Update a checklist item `PATCH https://evohub.io/api/v1/board/checklist-items/{itemID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `updateChecklistItem`. Ticks or unticks the item (`done`), changes its `text`, or both. A `text` left out or empty is not changed. `placement` is ignored here. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `itemID` (string, required, example `chk_1b2c3d4e5f6a47b8c9d0e1f2a3b4c5d6`): The checklist item's ID. ## Request body (required) Content type: `application/json` Type: `ChecklistRequest` - `text` (string, max length 500) - `done` (boolean) - `placement` (Placement) - `after` (string): The ID of the item it should follow. Empty means the top. - `before` (string): The ID of the item it should precede. Empty means the bottom. - `to` (string, one of `top`, `bottom`): An end of the list. Any other value is ignored. ## Responses ### 204 — The item was updated. ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/board/checklist-items/chk_1b2c3d4e5f6a47b8c9d0e1f2a3b4c5d6' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "done": true }' ``` --- Source: https://docs.evohub.io/board/delete-comment.md # Delete a comment `DELETE https://evohub.io/api/v1/board/comments/{commentID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `deleteComment`. Deletes the comment. Its author may delete it, and so may a caller that also holds `board:board:write`. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `commentID` (string, required, example `cmt_2c3d4e5f6a7b48c9d0e1f2a3b4c5d6e7`): The comment's ID. ## Responses ### 204 — The comment was deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks `board:card:write`, or the caller is neither the author nor holds `board:board:write` (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/board/comments/cmt_2c3d4e5f6a7b48c9d0e1f2a3b4c5d6e7' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/edit-comment.md # Edit a comment `PATCH https://evohub.io/api/v1/board/comments/{commentID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `editComment`. Replaces the comment's `body` (at most 10,000 characters) and marks it edited. Only the comment's author may edit it: for a personal key that is its owner, for an organization key the key itself. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `commentID` (string, required, example `cmt_2c3d4e5f6a7b48c9d0e1f2a3b4c5d6e7`): The comment's ID. ## Request body (required) Content type: `application/json` Type: `CommentRequest` - `body` (string, required, max length 10000) ## Responses ### 204 — The comment was edited. ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks `board:card:write`, or the caller is not the comment's author (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/board/comments/cmt_2c3d4e5f6a7b48c9d0e1f2a3b4c5d6e7' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "Staging is done; production moved to Tuesday." }' ``` --- Source: https://docs.evohub.io/board/list-card-links.md # List a card's links `GET https://evohub.io/api/v1/board/cards/{cardID}/links` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `listCardLinks`. The links from the card to things elsewhere. Requires the `board:card:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Responses ### 200 — The links. Content type: `application/json` Type: `object` - `data` (object) - `links` (array of Link) - `id` (string) - `card_id` (string) - `service` (string, example `oncall`) - `entity_type` (string, example `incident`) - `entity_id` (string) - `url` (string) - `title` (string) - `created_by` (string) - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/links' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/add-card-link.md # Link a card to something `POST https://evohub.io/api/v1/board/cards/{cardID}/links` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `addCardLink`. Links the card to a thing in another product, such as an incident or a monitor. `service` and `entity_type` are lowercase identifiers (letters, digits, and single `-`, `_` or `.` between them, at most 100 characters); `entity_id` is required (at most 100); `url` must be `http://` or `https://` (at most 2,048); `title` is optional (at most 200). A card holds one link per `service`, `entity_type` and `entity_id`: linking the same thing again updates its `url` and `title`. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Request body (required) Content type: `application/json` Type: `object` - `service` (string, required, max length 100) - `entity_type` (string, required, max length 100) - `entity_id` (string, required, max length 100) - `url` (string (uri), required, max length 2048) - `title` (string, max length 200) ## Responses ### 201 — The card is linked. Content type: `application/json` Type: `object` - `data` (object) - `link` (Link) - `id` (string) - `card_id` (string) - `service` (string, example `oncall`) - `entity_type` (string, example `incident`) - `entity_id` (string) - `url` (string) - `title` (string) - `created_by` (string) - `created_at` (string (date-time)) ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/links' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "url": "https://evohub.io/uptime/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19", "title": "Checkout API", "service": "uptime", "entity_id": "mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19", "entity_type": "monitor" }' ``` --- Source: https://docs.evohub.io/board/remove-card-link.md # Remove a link `DELETE https://evohub.io/api/v1/board/links/{linkID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `removeCardLink`. Removes the link from its card. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `linkID` (string, required, example `lnk_3d4e5f6a7b8c49d0e1f2a3b4c5d6e7f8`): The link's ID. ## Responses ### 204 — The link was removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/board/links/lnk_3d4e5f6a7b8c49d0e1f2a3b4c5d6e7f8' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/list-review-queue.md # List cards waiting for your review `GET https://evohub.io/api/v1/board/review-queue` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `listReviewQueue`. Cards waiting in a review list that the caller could approve, oldest first, on the boards the caller can see. Cards the caller is assigned to are left out, since they cannot approve them. `total` is how many are waiting in all, beyond the page. A caller without `board:review:write` cannot approve anything, so it gets an empty queue rather than a refusal. Requires the `board:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `limit` (integer, default `20`, min 1): Cards to return. Default 20; values above 50 are treated as 50. ## Responses ### 200 — The waiting cards. Content type: `application/json` Type: `object` - `data` (object) - `cards` (array of ReviewQueueCard) - `id` (string) - `title` (string) - `board_id` (string) - `board_name` (string) - `list_id` (string) - `list_name` (string) - `entered_review_at` (string (date-time)) - `due_at` (string (date-time) | null) - `total` (integer) ### 400 — `limit` is not a positive integer (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/review-queue' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/approve-card.md # Approve a card `POST https://evohub.io/api/v1/board/cards/{cardID}/approve` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `approveCard`. Signs off a card waiting in the review list. It stays where it is, now approved, and may be moved into the done list. Nobody assigned to the card may approve it; a card with nobody assigned can be approved by anyone with the scope. Requires the `board:review:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Responses ### 204 — The card is approved. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks `board:review:write` (`FORBIDDEN`), or the caller is assigned to the card (`CANNOT_APPROVE_OWN_WORK`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The card is not waiting for review (`NOT_IN_REVIEW`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/approve' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/reject-card.md # Reject a card `POST https://evohub.io/api/v1/board/cards/{cardID}/reject` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `rejectCard`. Sends a card waiting in review back to the list it came from. A `reason` (at most 10,000 characters) is posted as a comment on the card. Assignees may reject their own card. Send `{}` for no reason. Requires the `board:review:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Request body (required) Content type: `application/json` Type: `object` - `reason` (string, max length 10000) ## Responses ### 204 — The card is back in its earlier list. ### 400 — The body is not JSON for this endpoint, or carries a field it does not know (`INVALID_BODY`), or a field breaks a rule (`VALIDATION_ERROR`; the message says which). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The card is not waiting for review (`NOT_IN_REVIEW`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/reject' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "reason": "The production rotation is missing from the runbook." }' ``` --- Source: https://docs.evohub.io/board/list-archived-cards.md # List a board's archived cards `GET https://evohub.io/api/v1/board/boards/{boardID}/archive` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `listArchivedCards`. Cards that have left the board, most recently archived first. An archived card is deleted for good 730 days after it was archived (`deletes_at`). `total` counts the archive under the same search; `deleting_soon` counts the cards to be deleted within 30 days, whatever the search. Continue with `after` set to the last card's `id` while `has_more` is true. Requires the `board:card:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): The board's ID. ## Query parameters - `q` (string): Match card titles, case-insensitively. - `after` (string): Continue after this card ID. - `limit` (integer, default `50`, min 1, max 200): Cards per page. Default 50, at most 200. ## Responses ### 200 — A page of the archive. Content type: `application/json` Type: `object` - `data` (object) - `cards` (array of ArchivedCard) - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `state` (string, one of `pending`, `approved`) - `approved_by` (string) - `approved_at` (string (date-time)) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. - `archived_at` (string (date-time)) - `list_name` (string): The name of the list it was in. - `list_archived` (boolean): The list it came from is archived too. - `deletes_at` (string (date-time)): When the card will be deleted for good, 730 days after it was archived. - `total` (integer) - `has_more` (boolean) - `deleting_soon` (integer) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/boards/brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59/archive' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/get-archived-card.md # Get an archived card `GET https://evohub.io/api/v1/board/archive/cards/{cardID}` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `getArchivedCard`. One archived card with everything that was on it (`card`, the same shape as a card's details), and its archive entry (`archive`): the list it left, whether that list is archived too, and when the card will be deleted. A card that is not archived answers **404** here. Requires the `board:card:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Responses ### 200 — The archived card. Content type: `application/json` Type: `object` - `data` (object) - `card` (CardDetail) - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `state` (string, one of `pending`, `approved`) - `approved_by` (string) - `approved_at` (string (date-time)) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. - `assignees` (array of Assignee) - `user_id` (string) - `assigned_by` (string) - `assigned_at` (string (date-time)) - `labels` (array of Label) - `id` (string) - `board_id` (string) - `name` (string, max length 40) - `color` (string, example `#b91c1c`) - `created_at` (string (date-time)) - `checklist` (array of ChecklistItem) - `id` (string) - `card_id` (string) - `text` (string, max length 500) - `done` (boolean) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `comments` (array of Comment) - `id` (string) - `card_id` (string) - `author_id` (string) - `body` (string, max length 10000) - `edited_at` (string (date-time)): Present once the comment has been edited. - `created_at` (string (date-time)) - `links` (array of Link) - `id` (string) - `card_id` (string) - `service` (string, example `oncall`) - `entity_type` (string, example `incident`) - `entity_id` (string) - `url` (string) - `title` (string) - `created_by` (string) - `created_at` (string (date-time)) - `archive` (ArchivedCard) - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `state` (string, one of `pending`, `approved`) - `approved_by` (string) - `approved_at` (string (date-time)) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. - `archived_at` (string (date-time)) - `list_name` (string): The name of the list it was in. - `list_archived` (boolean): The list it came from is archived too. - `deletes_at` (string (date-time)): When the card will be deleted for good, 730 days after it was archived. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/archive/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/restore-archived-card.md # Restore an archived card `POST https://evohub.io/api/v1/board/archive/cards/{cardID}/restore` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `restoreArchivedCard`. Puts an archived card back on its board, at the bottom of the list it left. If that list is archived too, the card goes to the board's first list that is neither the review nor the done list. A restored card is no longer counted as finished. The answer says where it went. Requires the `board:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Responses ### 200 — The restored card. Content type: `application/json` Type: `CardEnvelope` - `data` (object) - `card` (Card) - `id` (string, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`) - `board_id` (string) - `list_id` (string) - `number` (integer): The card's number on its board, from 1. With the board's `card_prefix` it is the card's reference. - `title` (string, max length 200) - `description` (string, max length 20000) - `color` (string): A hex colour, or `""` for none. - `due_at` (string (date-time)): Absent when the card has no due date. - `review` (Review) - `state` (string, one of `pending`, `approved`) - `approved_by` (string) - `approved_at` (string (date-time)) - `completed_at` (string (date-time)): When the card entered a done list. Absent while it is not in one. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title or description. - `assignee_ids` (array of string | null): Who the card is on. Filled in when a board or list is read; `null` when one card is read on its own. - `label_ids` (array of string | null): The labels the card carries. Filled in when a board or list is read; `null` when one card is read on its own. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The board has no list to restore the card into (`NO_LIST_TO_RESTORE_INTO`). Add one first. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/board/archive/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/restore' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/list-board-activity.md # List activity across boards `GET https://evohub.io/api/v1/board/activity` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `listBoardActivity`. What happened on every board the caller can see, newest first. When a page is full it carries `next_before_seq`; pass it back as `before_seq` for the next page. It is absent on the last page. Requires the `board:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `before_seq` (integer, min 1): Return only events older than this sequence number. - `limit` (integer, min 1, max 200): Events per page. At most 200, which is also the default. ## Responses ### 200 — A page of events. Content type: `application/json` Type: `object` - `data` (object) - `events` (array of BoardActivity) - `seq` (integer) - `org_id` (string) - `board_id` (string) - `card_id` (string): Present for events about a card. - `actor_id` (string): The person, or organization key, that did it. - `kind` (string, example `card.moved`): What happened, such as `board.created`, `list.renamed`, `card.moved`, `card.approved`, `card.rejected`, `card.assigned`, `comment.added` or `card.auto_archived`. - `payload` (object): Details of the event; the keys depend on `kind`. - Other keys: any - `created_at` (string (date-time)) - `board_name` (string) - `next_before_seq` (integer): Present when there may be more; pass it as `before_seq`. ### 400 — `before_seq` or `limit` is not a positive integer (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/activity' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/get-card-history.md # Get a card's history `GET https://evohub.io/api/v1/board/cards/{cardID}/history` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `getCardHistory`. What has happened to the card, newest first, up to 200 events. Requires the `board:card:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `cardID` (string, required, example `crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90`): The card's ID. ## Responses ### 200 — The card's events. Content type: `application/json` Type: `object` - `data` (object) - `events` (array of Activity) - `seq` (integer) - `org_id` (string) - `board_id` (string) - `card_id` (string): Present for events about a card. - `actor_id` (string): The person, or organization key, that did it. - `kind` (string, example `card.moved`): What happened, such as `board.created`, `list.renamed`, `card.moved`, `card.approved`, `card.rejected`, `card.assigned`, `comment.added` or `card.auto_archived`. - `payload` (object): Details of the event; the keys depend on `kind`. - Other keys: any - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — It does not exist, or it is on a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/cards/crd_9f2e4d6c8b0a1e3f5d7c9b1a3e5f7d90/history' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/board/get-board-analytics.md # Get board analytics `GET https://evohub.io/api/v1/board/analytics` Part of the [Board API](https://docs.evohub.io/board.md) reference · operationId `getBoardAnalytics`. Throughput, cycle and lead time, cumulative flow, work in progress, overdue cards, review figures and a per-assignee breakdown, counted over the boards the caller can see. With neither `from` nor `to` the range is the last 30 UTC days, today included. A date (`YYYY-MM-DD`) is a UTC day and `to` names the last day included; an RFC 3339 timestamp is an exact instant and `to` is exclusive. A range may be at most 366 days. Ranges up to 48 hours are bucketed by hour, longer ones by day. Completed, cycle time and lead time are over the range; work in progress, backlog, overdue and pending review are the boards as they stand now. `compare` is `null` unless a comparison is asked for. Requires the `board:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `from` (string, example `2026-10-01`): Start of the range, `YYYY-MM-DD` or RFC 3339. Sent together with `to`. - `to` (string, example `2026-10-31`): End of the range, `YYYY-MM-DD` (included) or RFC 3339 (excluded). Sent together with `from`. - `compare` (string, one of `previous`, `year`): Also report the headline numbers of the previous period of the same length (`previous`) or the same range a year earlier (`year`). - `board_id` (string, example `brd_5b0c8e1a2f3d4c6b9a7e0d1f2c3b4a59`): Count only these boards, comma-separated, at most 100. Every one must be a board the caller can see, or the answer is **404**. - `team_id` (string): Count only one team's boards — its own and the ones given to it. ## Responses ### 200 — The analytics. Content type: `application/json` Type: `object` - `data` (BoardAnalytics) - `range` (object) - `from` (string (date-time)) - `to` (string (date-time)): Exclusive. - `granularity` (string, one of `hour`, `day`) - `tz` (string, value `UTC`) - `boards` (array of object): The boards counted. - `id` (string) - `name` (string) - `card_prefix` (string) - `team_id` (string) - `totals` (object) - `completed` (integer) - `cycle_time` (DurationStats) - `count` (integer) - `p50_seconds` (number | null) - `p85_seconds` (number | null) - `p95_seconds` (number | null) - `avg_seconds` (number | null) - `lead_time` (DurationStats) - `count` (integer) - `p50_seconds` (number | null) - `p85_seconds` (number | null) - `p95_seconds` (number | null) - `avg_seconds` (number | null) - `wip` (integer) - `backlog` (integer) - `overdue` (integer) - `pending_review` (integer) - `throughput` (object) - `series` (array of object): Cards finished per bucket. - `bucket` (string (date-time)) - `completed` (integer) - `weekly` (array of object): Cards finished per ISO week (Monday, UTC). `partial` is a week the range only partly covers. - `week_start` (string (date-time)) - `completed` (integer) - `partial` (boolean) - `scatter` (array of object): Finished cards with their cycle and lead time, the most recent 2,000. - `card_id` (string) - `board_id` (string) - `reference` (string) - `title` (string) - `completed_at` (string (date-time)) - `cycle_seconds` (number) - `lead_seconds` (number) - `scatter_truncated` (boolean) - `cumulative_flow` (object): How many cards were in each list at the end of each bucket; `counts` align with `buckets`. - `buckets` (array of string (date-time)) - `boards` (array of object) - `board_id` (string) - `lists` (array of object) - `list_id` (string) - `name` (string) - `is_backlog` (boolean): The board's first ordinary list, where cycle time starts. - `is_review_list` (boolean) - `is_done_list` (boolean) - `archived` (boolean) - `counts` (array of integer) - `wip` (array of object): What sits in each live list now. - `board_id` (string) - `list_id` (string) - `name` (string) - `is_backlog` (boolean) - `is_review_list` (boolean) - `is_done_list` (boolean) - `cards` (integer) - `overdue` (integer) - `pending_review` (integer) - `overdue` (object): Open cards past their due date, most overdue first, at most 100. - `total` (integer) - `cards` (array of object) - `card_id` (string) - `board_id` (string) - `list_id` (string) - `reference` (string) - `title` (string) - `due_at` (string (date-time)) - `assignees` (array of string) - `truncated` (boolean) - `review` (object): The review gate over the range. - `submitted` (integer) - `approved` (integer) - `rejected` (integer) - `approval_rate` (number | null) - `rework_cards` (integer): Cards rejected in the range that came back to review afterwards. - `rework_rate` (number | null) - `time_in_review` (DurationStats) - `count` (integer) - `p50_seconds` (number | null) - `p85_seconds` (number | null) - `p95_seconds` (number | null) - `avg_seconds` (number | null) - `time_to_approval` (DurationStats) - `count` (integer) - `p50_seconds` (number | null) - `p85_seconds` (number | null) - `p95_seconds` (number | null) - `avg_seconds` (number | null) - `time_to_rejection` (DurationStats) - `count` (integer) - `p50_seconds` (number | null) - `p85_seconds` (number | null) - `p95_seconds` (number | null) - `avg_seconds` (number | null) - `pending_now` (integer) - `oldest_pending_since` (string (date-time) | null) - `by_assignee` (array of object): Each person's share. `user_id` `""` is the cards with nobody on them. - `user_id` (string) - `completed` (integer) - `cycle_time` (DurationStats) - `count` (integer) - `p50_seconds` (number | null) - `p85_seconds` (number | null) - `p95_seconds` (number | null) - `avg_seconds` (number | null) - `wip` (integer) - `overdue` (integer) - `compare` (object | null): The comparison period's headline numbers, or `null`. - `mode` (string, one of `previous`, `year`) - `from` (string (date-time)) - `to` (string (date-time)) - `completed` (integer) - `cycle_time` (DurationStats) - `count` (integer) - `p50_seconds` (number | null) - `p85_seconds` (number | null) - `p95_seconds` (number | null) - `avg_seconds` (number | null) - `lead_time` (DurationStats) - `count` (integer) - `p50_seconds` (number | null) - `p85_seconds` (number | null) - `p95_seconds` (number | null) - `avg_seconds` (number | null) - `success` (boolean, value `true`) ### 400 — A parameter is wrong (`VALIDATION_FAILED`; `details` names each one): only one of `from` and `to`, a bound that is neither a date nor RFC 3339, `to` not after `from`, a range over 366 days, an unknown `compare`, or more than 100 boards. Content type: `application/json` Type: `ValidationFailed` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — A `board_id` names a board the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/board/analytics' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog.md # Catalog API Version 1.0 · OpenAPI 3.1.0 Read and describe EvoHub Catalog from scripts and CI: declare services with their owner, tier and links, read what agents and cloud accounts found running, decide the match queue, read drift, the service map and each service's SLA report, manage tiers, environments and cloud accounts, and export the inventory for an audit. **Authentication.** Every endpoint takes an EvoHub API key, sent as `Authorization: Bearer evohub_…` or as `X-API-Key: evohub_…`. Each operation names the scopes the key needs in its description and in `x-evohub-permission`; where two are listed, the key needs both. A missing or invalid key gets **401**; a valid key without the scope gets **403**, never 401. A key is held to its scopes even when it was created by an administrator. See the [API overview](/api-overview), [API keys and scopes](/api-keys-and-scopes) and [Errors](/errors). **Responses.** A successful answer carries its result in `data`. A list that pages also carries `pagination`: `next_cursor` (null on the last page), `has_more` and, on some lists' first page, `total`. Pass `next_cursor` back as `cursor` for the next page; `limit` is 20 by default and at most 100. Errors carry an `error` object with a machine-readable `code`. Timestamps are RFC 3339 in UTC. Request bodies are strict: a field the endpoint does not know is refused with `INVALID_BODY` rather than ignored. **Declared and observed.** Services, tiers, responsibilities and environments are what people declare. Resources, applications, images, changes and the map are what agents and cloud accounts observed; they are read-only here, except deciding which service a resource or application belongs to. **Agents.** The routes under `/api/v1/catalog/ingest/` are called by the EvoHub agent only, signed with the agent's own key. API keys cannot call them, and they are not part of this reference. ## Servers - `https://evohub.io` ## Authentication - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Services What your teams declared, its responsibilities and its SLA report. - [GET /api/v1/catalog/components](https://docs.evohub.io/catalog/list-components.md): List services - [POST /api/v1/catalog/components](https://docs.evohub.io/catalog/create-component.md): Declare a service - [GET /api/v1/catalog/components/{ref}](https://docs.evohub.io/catalog/get-component.md): Get a service - [DELETE /api/v1/catalog/components/{ref}](https://docs.evohub.io/catalog/delete-component.md): Delete a service - [PATCH /api/v1/catalog/components/{ref}](https://docs.evohub.io/catalog/update-component.md): Change a service - [GET /api/v1/catalog/components/{ref}/resources](https://docs.evohub.io/catalog/list-component-resources.md): List a service's resources - [PUT /api/v1/catalog/components/{ref}/responsibilities](https://docs.evohub.io/catalog/set-component-responsibilities.md): Set a service's RACI - [GET /api/v1/catalog/components/{ref}/sla-report](https://docs.evohub.io/catalog/get-component-slareport.md): Get a service's SLA report ## Matches Which service a resource or an application belongs to. - [GET /api/v1/catalog/matches](https://docs.evohub.io/catalog/list-matches.md): List match proposals - [POST /api/v1/catalog/matches/{id}/accept](https://docs.evohub.io/catalog/accept-match.md): Accept a proposal - [POST /api/v1/catalog/matches/{id}/reject](https://docs.evohub.io/catalog/reject-match.md): Reject a proposal - [POST /api/v1/catalog/resources/{id}/component](https://docs.evohub.io/catalog/bind-resource.md): Put a resource in a service - [POST /api/v1/catalog/applications/{id}/component](https://docs.evohub.io/catalog/bind-application.md): Put an application in a service ## Resources What agents and cloud accounts observed, its history and its changes. - [GET /api/v1/catalog/resources](https://docs.evohub.io/catalog/list-resources.md): List resources - [GET /api/v1/catalog/resources/{id}](https://docs.evohub.io/catalog/get-resource.md): Get a resource - [GET /api/v1/catalog/resources/{id}/changes](https://docs.evohub.io/catalog/list-resource-changes.md): List a resource's changes - [GET /api/v1/catalog/resources/{id}/changes/{change}/diff](https://docs.evohub.io/catalog/get-resource-change-diff.md): Get what a change changed - [GET /api/v1/catalog/changes](https://docs.evohub.io/catalog/list-changes.md): List recent changes - [GET /api/v1/catalog/images](https://docs.evohub.io/catalog/list-images.md): List container images ## Applications Resources grouped the way they run together, the namespaces and the service map. - [GET /api/v1/catalog/applications](https://docs.evohub.io/catalog/list-applications.md): List applications - [GET /api/v1/catalog/applications/{id}](https://docs.evohub.io/catalog/get-application.md): Get an application - [GET /api/v1/catalog/applications/{id}/graph](https://docs.evohub.io/catalog/get-application-graph.md): Get an application's graph - [GET /api/v1/catalog/namespaces](https://docs.evohub.io/catalog/list-namespaces.md): List namespaces - [GET /api/v1/catalog/map](https://docs.evohub.io/catalog/get-service-map.md): Get the service map ## Drift Where what was declared and what runs disagree. - [GET /api/v1/catalog/drift](https://docs.evohub.io/catalog/get-drift.md): Get drift ## Tiers The organization's tiers and the SLA targets they promise. - [GET /api/v1/catalog/tiers](https://docs.evohub.io/catalog/list-tiers.md): List tiers - [POST /api/v1/catalog/tiers](https://docs.evohub.io/catalog/create-tier.md): Create a tier - [PUT /api/v1/catalog/tiers/order](https://docs.evohub.io/catalog/reorder-tiers.md): Reorder tiers - [PATCH /api/v1/catalog/tiers/{id}](https://docs.evohub.io/catalog/update-tier.md): Change a tier - [POST /api/v1/catalog/tiers/{id}/archive](https://docs.evohub.io/catalog/archive-tier.md): Archive a tier - [GET /api/v1/catalog/tiers/{id}/versions](https://docs.evohub.io/catalog/list-tier-versions.md): List a tier's versions ## Environments The places things run, declared by people. - [GET /api/v1/catalog/environments](https://docs.evohub.io/catalog/list-environments.md): List environments - [POST /api/v1/catalog/environments](https://docs.evohub.io/catalog/create-environment.md): Create an environment - [GET /api/v1/catalog/environments/{id}](https://docs.evohub.io/catalog/get-environment.md): Get an environment - [DELETE /api/v1/catalog/environments/{id}](https://docs.evohub.io/catalog/delete-environment.md): Delete an environment - [PATCH /api/v1/catalog/environments/{id}](https://docs.evohub.io/catalog/update-environment.md): Change an environment ## Sources Where observations come from — agents and cloud accounts. - [GET /api/v1/catalog/sources](https://docs.evohub.io/catalog/list-sources.md): List sources - [GET /api/v1/catalog/sources/{id}](https://docs.evohub.io/catalog/get-source.md): Get a source - [PATCH /api/v1/catalog/sources/{id}](https://docs.evohub.io/catalog/update-source.md): Rename, pause or resume a source ## Cloud accounts AWS, Azure, GCP and portvMind accounts the catalog reads itself. - [GET /api/v1/catalog/connections/setup](https://docs.evohub.io/catalog/get-cloud-setup.md): Get what a connection needs - [GET /api/v1/catalog/connections](https://docs.evohub.io/catalog/list-connections.md): List cloud accounts - [POST /api/v1/catalog/connections](https://docs.evohub.io/catalog/create-connection.md): Connect a cloud account - [GET /api/v1/catalog/connections/{id}](https://docs.evohub.io/catalog/get-connection.md): Get a cloud account - [DELETE /api/v1/catalog/connections/{id}](https://docs.evohub.io/catalog/delete-connection.md): Disconnect a cloud account - [PATCH /api/v1/catalog/connections/{id}](https://docs.evohub.io/catalog/update-connection.md): Change a cloud account - [POST /api/v1/catalog/connections/{id}/pause](https://docs.evohub.io/catalog/pause-connection.md): Pause a cloud account - [POST /api/v1/catalog/connections/{id}/resume](https://docs.evohub.io/catalog/resume-connection.md): Resume a cloud account - [POST /api/v1/catalog/connections/{id}/check](https://docs.evohub.io/catalog/check-connection.md): Check a cloud account - [GET /api/v1/catalog/connections/{id}/template](https://docs.evohub.io/catalog/get-connection-template.md): Download the AWS role template - [GET /api/v1/catalog/connections/{id}/architecture](https://docs.evohub.io/catalog/get-connection-architecture.md): Get an account's architecture ## Export The inventory, for an audit. - [GET /api/v1/catalog/exports/inventory](https://docs.evohub.io/catalog/export-inventory.md): Export the inventory --- Source: https://docs.evohub.io/catalog/list-components.md # List services `GET https://evohub.io/api/v1/catalog/components` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listComponents`. A page of the organization's services, ordered by `sort`. For a key that also holds `catalog:resource:read`, each service carries `live_environments`: how many environments it runs in. Requires the `catalog:component:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `owner_team_id` (string, example `team_3b6f0e2a-1c4d-4e8f-9a7b-2d5c6e8f0a1b`): Only services owned by this team. - `tier` (integer, min 1, max 4): Only services whose tier is at this place in the order, 1 the most critical. - `lifecycle` (Lifecycle, one of `experimental`, `production`, `deprecated`, `retired`) - `kind` (ComponentKind, one of `service`, `library`, `database`, `website`, `job`, `other`) - `q` (string, max length 100): Search the name and slug, at most 100 characters. - `sort` (string, example `tier`): `slug` (the default), `kind`, `tier` (services with no tier last) or `lifecycle`; a leading `-` reverses it. Any other value is refused. - `limit` (integer, min 1, max 100): Page size, 20 by default, at most 100. - `cursor` (string): The previous page's `pagination.next_cursor`. ## Responses ### 200 — A page of services. Content type: `application/json` Type: `object` - `data` (array of Component & object) - `id` (string, example `cmp_7a1d3f5b9c2e4a6b8d0f1e3c5a7b9d2f`) - `org_id` (string) - `slug` (string, example `payments-api`) - `display_name` (string, example `Payments API`) - `description` (string) - `kind` (ComponentKind, one of `service`, `library`, `database`, `website`, `job`, `other`) - `owner_team_id` (string | null) - `tier_id` (string | null) - `tier` (integer | null): The tier's place in the order, 1 the most critical. - `sla_overrides` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `lifecycle` (Lifecycle, one of `experimental`, `production`, `deprecated`, `retired`) - `repo_url` (string) - `docs_url` (string) - `runbook_url` (string) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `live_environments` (integer): Environments it runs in. Only with `catalog:resource:read`. - `pagination` (Pagination) - `next_cursor` (string | null) - `has_more` (boolean) - `total` (integer): How many match, on the first page of the resource and application lists. ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/components' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/create-component.md # Declare a service `POST https://evohub.io/api/v1/catalog/components` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `createComponent`. Declares a service. `slug` and `display_name` are required. `kind` defaults to `service` and `lifecycle` to `production`. The slug is a DNS label (lowercase letters, digits and single hyphens, starting with a letter, at most 63 characters) and can never change. Resources already running whose `evohub.io/component` annotation names the new slug are matched to it straight away. Requires the `catalog:component:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `CreateComponentRequest` - `slug` (string, required, max length 63, pattern `^[a-z][a-z0-9]*(-[a-z0-9]+)*$`) - `display_name` (string, required, max length 100) - `description` (string, max length 2000) - `kind` (ComponentKind, one of `service`, `library`, `database`, `website`, `job`, `other`) - `owner_team_id` (string | null): A team ID (`team_…`), or null for no owner. - `tier_id` (string | null): One of the organization's tiers in use. - `tier` (integer | null): Instead of `tier_id`, the tier at this place in the order. - `sla_overrides` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `lifecycle` (Lifecycle, one of `experimental`, `production`, `deprecated`, `retired`) - `repo_url` (string): Empty, or an http(s) URL with a host and no user name or password, at most 2048 characters. The same for `docs_url` and `runbook_url`. - `docs_url` (string) - `runbook_url` (string) ## Responses ### 201 — The service. Content type: `application/json` Type: `ComponentEnvelope` - `data` (Component) - `id` (string, example `cmp_7a1d3f5b9c2e4a6b8d0f1e3c5a7b9d2f`) - `org_id` (string) - `slug` (string, example `payments-api`) - `display_name` (string, example `Payments API`) - `description` (string) - `kind` (ComponentKind, one of `service`, `library`, `database`, `website`, `job`, `other`) - `owner_team_id` (string | null) - `tier_id` (string | null) - `tier` (integer | null): The tier's place in the order, 1 the most critical. - `sla_overrides` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `lifecycle` (Lifecycle, one of `experimental`, `production`, `deprecated`, `retired`) - `repo_url` (string) - `docs_url` (string) - `runbook_url` (string) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — A service with this slug exists (`SLUG_TAKEN`), or the tier is archived (`TIER_ARCHIVED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/components' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "slug": "payments-api", "tier": 1, "description": "Takes card payments for checkout.", "runbook_url": "https://wiki.acme.example/runbooks/payments", "display_name": "Payments API", "owner_team_id": "team_3b6f0e2a-1c4d-4e8f-9a7b-2d5c6e8f0a1b" }' ``` --- Source: https://docs.evohub.io/catalog/get-component.md # Get a service `GET https://evohub.io/api/v1/catalog/components/{ref}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getComponent`. One service with its `sla` (its tier, its own overrides and the targets that result) and its `responsibilities`. For a key that also holds `catalog:resource:read`, `observations` adds its resource counts by status, the first page of its resources and the edges with it at either end. Requires the `catalog:component:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `ref` (string, required, example `payments-api`): The service's ID (`cmp_…`) or its slug. ## Responses ### 200 — The service. Content type: `application/json` Type: `object` - `data` (ComponentDetail) - `id` (string, example `cmp_7a1d3f5b9c2e4a6b8d0f1e3c5a7b9d2f`) - `org_id` (string) - `slug` (string, example `payments-api`) - `display_name` (string, example `Payments API`) - `description` (string) - `kind` (ComponentKind, one of `service`, `library`, `database`, `website`, `job`, `other`) - `owner_team_id` (string | null) - `tier_id` (string | null) - `tier` (integer | null): The tier's place in the order, 1 the most critical. - `sla_overrides` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `lifecycle` (Lifecycle, one of `experimental`, `production`, `deprecated`, `retired`) - `repo_url` (string) - `docs_url` (string) - `runbook_url` (string) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `sla` (object) - `tier` (Tier | null) - One of: - Tier - `id` (string) - `name` (string, example `Tier 1`) - `description` (string) - `rank` (integer): Its place in the order, 1 the most critical. - `color` (string, example `#d97706`) - `targets` (SLATargets) - `version` (integer) - `archived_at` (string (date-time) | null) - `services` (integer): How many services are in it. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - null - `overrides` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `targets` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `responsibilities` (array of Responsibility) - `role` (string, required, one of `responsible`, `accountable`, `consulted`, `informed`) - `team_id` (string | null) - `user_id` (string | null) - `observations` (object): Only with `catalog:resource:read`. - `resource_counts` (object) - Other keys: integer - `resources` (array of Resource) - `id` (string, example `res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b`) - `environment_id` (string) - `source_id` (string) - `external_id` (string, example `k8s://5f0c2a7e-…/apps/v1/Deployment/payments/payments-api`) - `provider` (string, example `k8s`) - `kind` (string, example `Deployment`) - `name` (string, example `payments-api`) - `namespace` (string, example `payments`) - `component_id` (string | null) - `component_bound_by` (string | null): What bound it to its service, when something did. - `status` (string, one of `live`, `missing`, `gone`, `stale`) - `missing_since` (string (date-time) | null) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) - `images` (array of string) - `resources_next_cursor` (string | null) - `edges` (array of Edge) - `id` (string) - `from_kind` (string) - `from_id` (string) - `to_kind` (string) - `to_id` (string) - `type` (string, example `runs_on`) - `source_id` (string) - `confidence` (number) - `observed_at` (string (date-time)) - `edges_truncated` (boolean) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/components/payments-api' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/delete-component.md # Delete a service `DELETE https://evohub.io/api/v1/catalog/components/{ref}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `deleteComponent`. Removes the declaration. Its resources stay and become unowned; its match proposals and the edges with it at either end go. Requires the `catalog:component:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `ref` (string, required, example `payments-api`): The service's ID (`cmp_…`) or its slug. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/catalog/components/payments-api' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/update-component.md # Change a service `PATCH https://evohub.io/api/v1/catalog/components/{ref}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `updateComponent`. Changes only the fields sent. Only `owner_team_id`, `tier`, `tier_id` and `sla_overrides` may be `null`; send `""` to clear a text field. A `slug` different from the current one is refused with `SLUG_IMMUTABLE`. Requires the `catalog:component:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `ref` (string, required, example `payments-api`): The service's ID (`cmp_…`) or its slug. ## Request body (required) Content type: `application/json` Type: `UpdateComponentRequest` - `slug` (string): Accepted only when it is the current slug. - `display_name` (string) - `description` (string) - `kind` (ComponentKind, one of `service`, `library`, `database`, `website`, `job`, `other`) - `owner_team_id` (string | null) - `tier_id` (string | null) - `tier` (integer | null) - `sla_overrides` (SLATargets | null) - One of: - SLATargets - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - null - `lifecycle` (Lifecycle, one of `experimental`, `production`, `deprecated`, `retired`) - `repo_url` (string) - `docs_url` (string) - `runbook_url` (string) ## Responses ### 200 — The service. Content type: `application/json` Type: `ComponentEnvelope` - `data` (Component) - `id` (string, example `cmp_7a1d3f5b9c2e4a6b8d0f1e3c5a7b9d2f`) - `org_id` (string) - `slug` (string, example `payments-api`) - `display_name` (string, example `Payments API`) - `description` (string) - `kind` (ComponentKind, one of `service`, `library`, `database`, `website`, `job`, `other`) - `owner_team_id` (string | null) - `tier_id` (string | null) - `tier` (integer | null): The tier's place in the order, 1 the most critical. - `sla_overrides` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `lifecycle` (Lifecycle, one of `experimental`, `production`, `deprecated`, `retired`) - `repo_url` (string) - `docs_url` (string) - `runbook_url` (string) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The tier is archived (`TIER_ARCHIVED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/catalog/components/payments-api' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "lifecycle": "deprecated", "owner_team_id": null }' ``` --- Source: https://docs.evohub.io/catalog/list-component-resources.md # List a service's resources `GET https://evohub.io/api/v1/catalog/components/{ref}/resources` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listComponentResources`. A page of the resources bound to the service. Requires the `catalog:component:read` and `catalog:resource:read` scopes. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `ref` (string, required, example `payments-api`): The service's ID (`cmp_…`) or its slug. ## Query parameters - `environment_id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): Only this environment. - `status` (string, one of `live`, `missing`, `gone`, `stale`): `live` (seen by the last sweep), `missing` (not seen by the last complete sweep), `gone` (missing for a day) or `stale` (its agent stopped reporting). - `limit` (integer, min 1, max 100): Page size, 20 by default, at most 100. - `cursor` (string): The previous page's `pagination.next_cursor`. ## Responses ### 200 — A page of resources. Content type: `application/json` Type: `object` - `data` (array of Resource) - `id` (string, example `res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b`) - `environment_id` (string) - `source_id` (string) - `external_id` (string, example `k8s://5f0c2a7e-…/apps/v1/Deployment/payments/payments-api`) - `provider` (string, example `k8s`) - `kind` (string, example `Deployment`) - `name` (string, example `payments-api`) - `namespace` (string, example `payments`) - `component_id` (string | null) - `component_bound_by` (string | null): What bound it to its service, when something did. - `status` (string, one of `live`, `missing`, `gone`, `stale`) - `missing_since` (string (date-time) | null) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) - `images` (array of string) - `pagination` (Pagination) - `next_cursor` (string | null) - `has_more` (boolean) - `total` (integer): How many match, on the first page of the resource and application lists. ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/components/payments-api/resources' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/set-component-responsibilities.md # Set a service's RACI `PUT https://evohub.io/api/v1/catalog/components/{ref}/responsibilities` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `setComponentResponsibilities`. Replaces the service's RACI as a whole; an empty list clears it. Each entry is a role (`responsible`, `accountable`, `consulted`, `informed`) and exactly one of `team_id` or `user_id`. At most 50 entries, none twice, and exactly one `accountable` when there are any. The owner team stays the owner. Requires the `catalog:component:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `ref` (string, required, example `payments-api`): The service's ID (`cmp_…`) or its slug. ## Request body (required) Content type: `application/json` Type: `object` - `responsibilities` (array of Responsibility, required, max items 50) - `role` (string, required, one of `responsible`, `accountable`, `consulted`, `informed`) - `team_id` (string | null) - `user_id` (string | null) ## Responses ### 200 — The RACI as stored. Content type: `application/json` Type: `object` - `data` (array of Responsibility) - `role` (string, required, one of `responsible`, `accountable`, `consulted`, `informed`) - `team_id` (string | null) - `user_id` (string | null) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/catalog/components/payments-api/responsibilities' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "responsibilities": [ { "role": "accountable", "team_id": "team_3b6f0e2a-1c4d-4e8f-9a7b-2d5c6e8f0a1b" }, { "role": "consulted", "user_id": "usr_9f2d4c6b-8a1e-4f3d-b5c7-0e2a4c6e8b1d" } ] }' ``` --- Source: https://docs.evohub.io/catalog/get-component-slareport.md # Get a service's SLA report `GET https://evohub.io/api/v1/catalog/components/{ref}/sla-report` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getComponentSLAReport`. One calendar month in UTC, the current one by default (measured up to now, `complete: false`). The targets are the service's tier as it read at the end of the month, with the service's own overrides. Availability is summed over the Uptime monitors tied to the service; response and resolution come from the On-Call alerts raised by the integrations and monitors tied to it, per severity. Each part says `measured`, `not_connected` or `unavailable`; one part that cannot be measured never fails the report. Measurement runs around the clock: support hours are shown but not applied yet. Requires the `catalog:component:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `ref` (string, required, example `payments-api`): The service's ID (`cmp_…`) or its slug. ## Query parameters - `month` (string, pattern `^[0-9]{4}-[0-9]{2}$`, example `2026-10`): `YYYY-MM`, from 2020-01 to the current month. ## Responses ### 200 — The report. Content type: `application/json` Type: `object` - `data` (SLAReport) - `component_id` (string) - `month` (string, example `2026-10`) - `from` (string) - `to` (string) - `complete` (boolean): False for the current month, measured up to now. - `tier` (object | null) - `id` (string) - `name` (string) - `version` (integer) - `targets` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `availability` (object) - `status` (MeasureStatus, one of `measured`, `not_connected`, `unavailable`) - `target` (number | null) - `uptime` (number | null) - `total_checks` (integer) - `up_checks` (integer) - `met` (boolean | null) - `monitors` (array of object) - `id` (string) - `name` (string) - `total_checks` (integer) - `up_checks` (integer) - `uptime` (number | null) - `alerts` (object) - `status` (MeasureStatus, one of `measured`, `not_connected`, `unavailable`) - `severities` (array of object) - `severity` (string, example `critical`) - `alerts` (integer) - `acknowledged` (integer) - `resolved` (integer) - `open` (integer) - `response_target` (integer | null) - `resolution_target` (integer | null) - `ack_p50_s` (number | null) - `ack_p90_s` (number | null) - `resolve_p50_s` (number | null) - `resolve_p90_s` (number | null) - `ack_breaches` (integer) - `resolve_breaches` (integer) - `response_met` (boolean | null) - `resolution_met` (boolean | null) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/components/payments-api/sla-report' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/list-matches.md # List match proposals `GET https://evohub.io/api/v1/catalog/matches` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listMatches`. The match queue: proposals that a resource or an application belongs to a service, strongest first. Each names its resource or application, and either an existing service (`component`) or a slug nobody declared yet (`proposed_slug`). `confidence` orders the queue and never accepts anything on its own. Requires the `catalog:component:read` and `catalog:resource:read` scopes. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `status` (string, one of `proposed`, `accepted`, `rejected`): `proposed` (the default), `accepted` or `rejected`. - `component_id` (string): Only proposals for this service. - `limit` (integer, min 1, max 100): Page size, 20 by default, at most 100. - `cursor` (string): The previous page's `pagination.next_cursor`. ## Responses ### 200 — A page of proposals. Content type: `application/json` Type: `object` - `data` (array of Match) - `id` (string) - `resource_id` (string | null) - `application_id` (string | null) - `component_id` (string | null): The service proposed, when it exists. - `proposed_slug` (string | null): A slug nobody declared yet; accepting creates that service. - `reason` (string): Which rule proposed it. - `confidence` (number) - `status` (string, one of `proposed`, `accepted`, `rejected`) - `decided_by` (string | null) - `decided_at` (string (date-time) | null) - `created_at` (string (date-time)) - `resource` (object | null) - `environment_id` (string) - `provider` (string) - `kind` (string) - `name` (string) - `namespace` (string) - `status` (string) - `application` (object | null) - `environment_id` (string) - `namespace` (string) - `name` (string) - `key_source` (string) - `owner_hint` (string | null) - `component` (object | null) - `slug` (string) - `display_name` (string) - `pagination` (Pagination) - `next_cursor` (string | null) - `has_more` (boolean) - `total` (integer): How many match, on the first page of the resource and application lists. ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/matches' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/accept-match.md # Accept a proposal `POST https://evohub.io/api/v1/catalog/matches/{id}/accept` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `acceptMatch`. Binds the proposal's resource, or its application and every member, to the service. A proposal for a slug nobody declared creates that service, which also needs `catalog:component:write` (403 `FORBIDDEN` without it). Requires the `catalog:resource:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mch_3e5a7c9b1d2f4a6c8e0b2d4f6a8c1e3b`): The proposal's ID. ## Responses ### 200 — The proposal, decided. Content type: `application/json` Type: `object` - `data` (Match) - `id` (string) - `resource_id` (string | null) - `application_id` (string | null) - `component_id` (string | null): The service proposed, when it exists. - `proposed_slug` (string | null): A slug nobody declared yet; accepting creates that service. - `reason` (string): Which rule proposed it. - `confidence` (number) - `status` (string, one of `proposed`, `accepted`, `rejected`) - `decided_by` (string | null) - `decided_at` (string (date-time) | null) - `created_at` (string (date-time)) - `resource` (object | null) - `environment_id` (string) - `provider` (string) - `kind` (string) - `name` (string) - `namespace` (string) - `status` (string) - `application` (object | null) - `environment_id` (string) - `namespace` (string) - `name` (string) - `key_source` (string) - `owner_hint` (string | null) - `component` (object | null) - `slug` (string) - `display_name` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The proposal was already accepted or rejected (`MATCH_DECIDED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/matches/mch_3e5a7c9b1d2f4a6c8e0b2d4f6a8c1e3b/accept' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/reject-match.md # Reject a proposal `POST https://evohub.io/api/v1/catalog/matches/{id}/reject` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `rejectMatch`. Refuses the proposal for good: the same pair is never proposed again. Requires the `catalog:resource:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mch_3e5a7c9b1d2f4a6c8e0b2d4f6a8c1e3b`): The proposal's ID. ## Responses ### 200 — The proposal, decided. Content type: `application/json` Type: `object` - `data` (Match) - `id` (string) - `resource_id` (string | null) - `application_id` (string | null) - `component_id` (string | null): The service proposed, when it exists. - `proposed_slug` (string | null): A slug nobody declared yet; accepting creates that service. - `reason` (string): Which rule proposed it. - `confidence` (number) - `status` (string, one of `proposed`, `accepted`, `rejected`) - `decided_by` (string | null) - `decided_at` (string (date-time) | null) - `created_at` (string (date-time)) - `resource` (object | null) - `environment_id` (string) - `provider` (string) - `kind` (string) - `name` (string) - `namespace` (string) - `status` (string) - `application` (object | null) - `environment_id` (string) - `namespace` (string) - `name` (string) - `key_source` (string) - `owner_hint` (string | null) - `component` (object | null) - `slug` (string) - `display_name` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The proposal was already accepted or rejected (`MATCH_DECIDED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/matches/mch_3e5a7c9b1d2f4a6c8e0b2d4f6a8c1e3b/reject' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/bind-resource.md # Put a resource in a service `POST https://evohub.io/api/v1/catalog/resources/{id}/component` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `bindResource`. Binds the resource to a service by hand (`{"component": ""}`), or unbinds it (`{"component": null}`). The key is required. A binding made here is never undone by a sweep. Unbinding a resource whose `evohub.io/component` annotation names a service lasts only until its next sweep: change the manifest to move it for good. Requires the `catalog:resource:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b`): The resource's ID. ## Request body (required) Content type: `application/json` Type: `BindRequest` - `component` (string | null, required): A service's ID or slug, or null to unbind. ## Responses ### 200 — The resource. Content type: `application/json` Type: `object` - `data` (Resource) - `id` (string, example `res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b`) - `environment_id` (string) - `source_id` (string) - `external_id` (string, example `k8s://5f0c2a7e-…/apps/v1/Deployment/payments/payments-api`) - `provider` (string, example `k8s`) - `kind` (string, example `Deployment`) - `name` (string, example `payments-api`) - `namespace` (string, example `payments`) - `component_id` (string | null) - `component_bound_by` (string | null): What bound it to its service, when something did. - `status` (string, one of `live`, `missing`, `gone`, `stale`) - `missing_since` (string (date-time) | null) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) - `images` (array of string) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/resources/res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b/component' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "component": "payments-api" }' ``` --- Source: https://docs.evohub.io/catalog/bind-application.md # Put an application in a service `POST https://evohub.io/api/v1/catalog/applications/{id}/component` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `bindApplication`. Binds the application, and every member now and as members come and go, to a service (`{"component": ""}`), or unbinds it (`{"component": null}`). A member bound on its own, by its annotation or by hand, keeps its own binding. Requires the `catalog:resource:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `app_9c1e3a5b7d9f2b4d6f8a0c2e4b6d8f1a`): The application's ID. ## Request body (required) Content type: `application/json` Type: `BindRequest` - `component` (string | null, required): A service's ID or slug, or null to unbind. ## Responses ### 200 — The application. Content type: `application/json` Type: `object` - `data` (Application) - `id` (string) - `environment_id` (string) - `namespace` (string) - `provider` (string) - `key_source` (string): What grouped it, such as an Argo CD application or a Helm release. - `key` (string) - `name` (string) - `component_id` (string | null) - `component_bound_by` (string | null) - `owner_hint` (string | null): The team its `evohub.io/team`, `team` or `owner` label names. - `status` (string, one of `live`, `missing`, `gone`) - `members` (integer) - `by_status` (object) - Other keys: integer - `kinds` (object) - Other keys: integer - `images` (array of string) - `health` (Health, one of `healthy`, `degraded`, `down`, `idle`, `unknown`) - `ready` (integer) - `desired` (integer) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/applications/app_9c1e3a5b7d9f2b4d6f8a0c2e4b6d8f1a/component' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "component": "cmp_7a1d3f5b9c2e4a6b8d0f1e3c5a7b9d2f" }' ``` --- Source: https://docs.evohub.io/catalog/list-resources.md # List resources `GET https://evohub.io/api/v1/catalog/resources` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listResources`. A page of what the organization's sources observed. The first page (a request without `cursor`) carries `pagination.total`. Requires the `catalog:resource:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `environment_id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): Only this environment. - `provider` (string): `k8s`, `host`, `aws`, `azure`, `gcp` or `vmind`, as the resource was reported. - `kind` (string, example `Deployment`) - `exclude_kind` (string, example `Namespace,Node`): Kinds to leave out, comma-separated. - `namespace` (string) - `application_id` (string) - `status` (string, one of `live`, `missing`, `gone`, `stale`): `live` (seen by the last sweep), `missing` (not seen by the last complete sweep), `gone` (missing for a day) or `stale` (its agent stopped reporting). - `component_id` (string): Only resources bound to this service. - `unowned` (string, value `true`): `true` for live resources bound to no service. - `image` (string): A whole image reference (`nginx:1.25-alpine`), or a repository (`nginx`) for any tag or digest of it. - `floating` (string, value `true`): `true` for resources running an image with no fixed version: `latest` or no tag, and no digest. - `q` (string, max length 100): Search names, at most 100 characters. - `sort` (string, example `-last_seen_at`): `name` (the default), `kind`, `status`, `first_seen_at` or `last_seen_at`; a leading `-` reverses it. Any other value is refused. - `limit` (integer, min 1, max 100): Page size, 20 by default, at most 100. - `cursor` (string): The previous page's `pagination.next_cursor`. ## Responses ### 200 — A page of resources. Content type: `application/json` Type: `object` - `data` (array of Resource) - `id` (string, example `res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b`) - `environment_id` (string) - `source_id` (string) - `external_id` (string, example `k8s://5f0c2a7e-…/apps/v1/Deployment/payments/payments-api`) - `provider` (string, example `k8s`) - `kind` (string, example `Deployment`) - `name` (string, example `payments-api`) - `namespace` (string, example `payments`) - `component_id` (string | null) - `component_bound_by` (string | null): What bound it to its service, when something did. - `status` (string, one of `live`, `missing`, `gone`, `stale`) - `missing_since` (string (date-time) | null) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) - `images` (array of string) - `pagination` (Pagination) - `next_cursor` (string | null) - `has_more` (boolean) - `total` (integer): How many match, on the first page of the resource and application lists. ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/resources' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/get-resource.md # Get a resource `GET https://evohub.io/api/v1/catalog/resources/{id}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getResource`. One resource with its current `state` (the redacted document its source sent), its `connections` to other resources, its `application` and, for a workload, its latest `live` report. For a key that also holds `catalog:component:read`, `proposals` lists the match proposals waiting on it. Requires the `catalog:resource:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b`): The resource's ID. ## Responses ### 200 — The resource. Content type: `application/json` Type: `object` - `data` (ResourceDetail) - `id` (string, example `res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b`) - `environment_id` (string) - `source_id` (string) - `external_id` (string, example `k8s://5f0c2a7e-…/apps/v1/Deployment/payments/payments-api`) - `provider` (string, example `k8s`) - `kind` (string, example `Deployment`) - `name` (string, example `payments-api`) - `namespace` (string, example `payments`) - `component_id` (string | null) - `component_bound_by` (string | null): What bound it to its service, when something did. - `status` (string, one of `live`, `missing`, `gone`, `stale`) - `missing_since` (string (date-time) | null) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) - `images` (array of string) - `state` (object | null) - `id` (string) - `content_hash` (string) - `payload` (object): The redacted document its source sent. - `observed_at` (string (date-time)) - `proposals` (array of Match): Only with `catalog:component:read`. - `id` (string) - `resource_id` (string | null) - `application_id` (string | null) - `component_id` (string | null): The service proposed, when it exists. - `proposed_slug` (string | null): A slug nobody declared yet; accepting creates that service. - `reason` (string): Which rule proposed it. - `confidence` (number) - `status` (string, one of `proposed`, `accepted`, `rejected`) - `decided_by` (string | null) - `decided_at` (string (date-time) | null) - `created_at` (string (date-time)) - `resource` (object | null) - `environment_id` (string) - `provider` (string) - `kind` (string) - `name` (string) - `namespace` (string) - `status` (string) - `application` (object | null) - `environment_id` (string) - `namespace` (string) - `name` (string) - `key_source` (string) - `owner_hint` (string | null) - `component` (object | null) - `slug` (string) - `display_name` (string) - `connections` (array of object) - `direction` (string, one of `in`, `out`) - `type` (string, example `exposes`) - `other_id` (string | null) - `other_kind` (string) - `other_name` (string) - `other_namespace` (string) - `declared` (boolean): True for a link people declared, false for one observed. - `connections_truncated` (boolean) - `application` (object | null) - `id` (string) - `name` (string) - `key_source` (string) - `live` (Live | null) - One of: - Live - `desired` (integer) - `ready` (integer) - `available` (integer) - `updated` (integer) - `pods` (object): Pods by phase. - Other keys: integer - `restarts` (integer) - `reasons` (object): Containers in each failing state, such as `CrashLoopBackOff`. - Other keys: integer - `pods_seen` (boolean) - `observed_at` (string (date-time)) - `health` (Health, one of `healthy`, `degraded`, `down`, `idle`, `unknown`) - null ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/resources/res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/list-resource-changes.md # List a resource's changes `GET https://evohub.io/api/v1/catalog/resources/{id}/changes` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listResourceChanges`. Every change its sources observed, newest first. Requires the `catalog:resource:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b`): The resource's ID. ## Query parameters - `limit` (integer, min 1, max 100): Page size, 20 by default, at most 100. - `cursor` (string): The previous page's `pagination.next_cursor`. ## Responses ### 200 — A page of changes. Content type: `application/json` Type: `object` - `data` (array of Change) - `id` (string) - `resource_id` (string) - `from_state_id` (string | null) - `to_state_id` (string) - `changed_fields` (array of string) - `observed_at` (string (date-time)) - `source_id` (string) - `resource` (object): The resource, on the organization-wide list. - `environment_id` (string) - `provider` (string) - `kind` (string) - `name` (string) - `namespace` (string) - `component_id` (string | null) - `pagination` (Pagination) - `next_cursor` (string | null) - `has_more` (boolean) - `total` (integer): How many match, on the first page of the resource and application lists. ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/resources/res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b/changes' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/get-resource-change-diff.md # Get what a change changed `GET https://evohub.io/api/v1/catalog/resources/{id}/changes/{change}/diff` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getResourceChangeDiff`. The fields one change touched, each with its value before and after. List elements are compared by position; values are capped at 2 KB and the list at 100 fields (`truncated`). Requires the `catalog:resource:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b`): The resource's ID. - `change` (string, required, example `rch_5c2e8a1f4b7d4e9a8c3f6b0d2e4a7c91`): The change's ID. ## Responses ### 200 — The diff. Content type: `application/json` Type: `object` - `data` (ChangeDiff) - `change_id` (string) - `fields` (array of object) - `path` (string, example `spec.template.spec.containers[0].image`) - `from` (any) - `to` (any) - `truncated` (boolean) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/resources/res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b/changes/rch_5c2e8a1f4b7d4e9a8c3f6b0d2e4a7c91/diff' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/list-changes.md # List recent changes `GET https://evohub.io/api/v1/catalog/changes` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listChanges`. Changes across the organization, newest first, each with the resource it happened to. Requires the `catalog:resource:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `from` (string (date-time), example `2026-10-13T09:00:00Z`): RFC 3339; must be before `to`. - `to` (string (date-time)): RFC 3339. - `environment_id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): Only this environment. - `component_id` (string) - `limit` (integer, min 1, max 100): Page size, 20 by default, at most 100. - `cursor` (string): The previous page's `pagination.next_cursor`. ## Responses ### 200 — A page of changes. Content type: `application/json` Type: `object` - `data` (array of Change) - `id` (string) - `resource_id` (string) - `from_state_id` (string | null) - `to_state_id` (string) - `changed_fields` (array of string) - `observed_at` (string (date-time)) - `source_id` (string) - `resource` (object): The resource, on the organization-wide list. - `environment_id` (string) - `provider` (string) - `kind` (string) - `name` (string) - `namespace` (string) - `component_id` (string | null) - `pagination` (Pagination) - `next_cursor` (string | null) - `has_more` (boolean) - `total` (integer): How many match, on the first page of the resource and application lists. ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/changes' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/list-images.md # List container images `GET https://evohub.io/api/v1/catalog/images` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listImages`. Every container image the organization's resources run, most used first, with how many resources run it, in which environments, and whether it is floating (`latest` or no tag, and no digest). Requires the `catalog:resource:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `q` (string, max length 100): Search image references. - `environment_id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): Only this environment. ## Responses ### 200 — The images. Content type: `application/json` Type: `object` - `data` (array of ImageUsage) - `image` (string, example `nginx:1.25-alpine`) - `repository` (string) - `tag` (string) - `digest` (string) - `floating` (boolean) - `resources` (integer) - `environments` (array of string) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/images' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/list-applications.md # List applications `GET https://evohub.io/api/v1/catalog/applications` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listApplications`. A page of applications, by namespace and name. An application is what runs together: an Argo CD application, a Helm release, an `app.kubernetes.io/instance` label, or otherwise one workload with its Services, Ingresses, autoscaler and volume claims. On a VM, a Compose project, a container or a systemd service. The first page carries `pagination.total`. Requires the `catalog:resource:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `environment_id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): Only this environment. - `namespace` (string) - `status` (string): `live`, `missing` or `gone`. - `component_id` (string) - `unowned` (string, value `true`): `true` for applications bound to no service. - `q` (string, max length 100) - `limit` (integer, min 1, max 100): Page size, 20 by default, at most 100. - `cursor` (string): The previous page's `pagination.next_cursor`. ## Responses ### 200 — A page of applications. Content type: `application/json` Type: `object` - `data` (array of Application) - `id` (string) - `environment_id` (string) - `namespace` (string) - `provider` (string) - `key_source` (string): What grouped it, such as an Argo CD application or a Helm release. - `key` (string) - `name` (string) - `component_id` (string | null) - `component_bound_by` (string | null) - `owner_hint` (string | null): The team its `evohub.io/team`, `team` or `owner` label names. - `status` (string, one of `live`, `missing`, `gone`) - `members` (integer) - `by_status` (object) - Other keys: integer - `kinds` (object) - Other keys: integer - `images` (array of string) - `health` (Health, one of `healthy`, `degraded`, `down`, `idle`, `unknown`) - `ready` (integer) - `desired` (integer) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) - `pagination` (Pagination) - `next_cursor` (string | null) - `has_more` (boolean) - `total` (integer): How many match, on the first page of the resource and application lists. ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/applications' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/get-application.md # Get an application `GET https://evohub.io/api/v1/catalog/applications/{id}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getApplication`. An application and its member resources, the gone ones last. For a key that also holds `catalog:component:read`, `proposals` lists the match proposals waiting on it. Requires the `catalog:resource:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `app_9c1e3a5b7d9f2b4d6f8a0c2e4b6d8f1a`): The application's ID. ## Responses ### 200 — The application. Content type: `application/json` Type: `object` - `data` (ApplicationDetail) - `id` (string) - `environment_id` (string) - `namespace` (string) - `provider` (string) - `key_source` (string): What grouped it, such as an Argo CD application or a Helm release. - `key` (string) - `name` (string) - `component_id` (string | null) - `component_bound_by` (string | null) - `owner_hint` (string | null): The team its `evohub.io/team`, `team` or `owner` label names. - `status` (string, one of `live`, `missing`, `gone`) - `members` (integer) - `by_status` (object) - Other keys: integer - `kinds` (object) - Other keys: integer - `images` (array of string) - `health` (Health, one of `healthy`, `degraded`, `down`, `idle`, `unknown`) - `ready` (integer) - `desired` (integer) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) - `resources` (array of Resource) - `id` (string, example `res_6b8d0f2a4c6e8a1c3e5b7d9f1a3c5e7b`) - `environment_id` (string) - `source_id` (string) - `external_id` (string, example `k8s://5f0c2a7e-…/apps/v1/Deployment/payments/payments-api`) - `provider` (string, example `k8s`) - `kind` (string, example `Deployment`) - `name` (string, example `payments-api`) - `namespace` (string, example `payments`) - `component_id` (string | null) - `component_bound_by` (string | null): What bound it to its service, when something did. - `status` (string, one of `live`, `missing`, `gone`, `stale`) - `missing_since` (string (date-time) | null) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) - `images` (array of string) - `resources_truncated` (boolean) - `proposals` (array of Match): Only with `catalog:component:read`. - `id` (string) - `resource_id` (string | null) - `application_id` (string | null) - `component_id` (string | null): The service proposed, when it exists. - `proposed_slug` (string | null): A slug nobody declared yet; accepting creates that service. - `reason` (string): Which rule proposed it. - `confidence` (number) - `status` (string, one of `proposed`, `accepted`, `rejected`) - `decided_by` (string | null) - `decided_at` (string (date-time) | null) - `created_at` (string (date-time)) - `resource` (object | null) - `environment_id` (string) - `provider` (string) - `kind` (string) - `name` (string) - `namespace` (string) - `status` (string) - `application` (object | null) - `environment_id` (string) - `namespace` (string) - `name` (string) - `key_source` (string) - `owner_hint` (string | null) - `component` (object | null) - `slug` (string) - `display_name` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/applications/app_9c1e3a5b7d9f2b4d6f8a0c2e4b6d8f1a' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/get-application-graph.md # Get an application's graph `GET https://evohub.io/api/v1/catalog/applications/{id}/graph` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getApplicationGraph`. The application's members and the edges between them (at most 500), with what they reach outside it named. ConfigMaps and Secrets appear by name only. While the agent's live report is current, a workload's pods are drawn under it, each with its own health. Requires the `catalog:resource:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `app_9c1e3a5b7d9f2b4d6f8a0c2e4b6d8f1a`): The application's ID. ## Responses ### 200 — The graph. Content type: `application/json` Type: `object` - `data` (AppGraph) - `application` (Application) - `id` (string) - `environment_id` (string) - `namespace` (string) - `provider` (string) - `key_source` (string): What grouped it, such as an Argo CD application or a Helm release. - `key` (string) - `name` (string) - `component_id` (string | null) - `component_bound_by` (string | null) - `owner_hint` (string | null): The team its `evohub.io/team`, `team` or `owner` label names. - `status` (string, one of `live`, `missing`, `gone`) - `members` (integer) - `by_status` (object) - Other keys: integer - `kinds` (object) - Other keys: integer - `images` (array of string) - `health` (Health, one of `healthy`, `degraded`, `down`, `idle`, `unknown`) - `ready` (integer) - `desired` (integer) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) - `nodes` (array of object) - `id` (string) - `resource_id` (string | null) - `provider` (string) - `kind` (string) - `name` (string) - `namespace` (string) - `status` (string | null) - `member` (boolean): False for something outside the application that a member reaches. - `application_id` (string | null) - `application_name` (string | null) - `health` (string | null) - `pod` (object): For a pod node, its line from the live report. - `current` (boolean): On a ReplicaSet, whether the newest pod belongs to it. - `edges` (array of object) - `from` (string) - `to` (string) - `type` (string) - `truncated` (boolean) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/applications/app_9c1e3a5b7d9f2b4d6f8a0c2e4b6d8f1a/graph' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/list-namespaces.md # List namespaces `GET https://evohub.io/api/v1/catalog/namespaces` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listNamespaces`. Every namespace that holds an application, with its counts of applications, resources and unowned resources. Requires the `catalog:resource:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `environment_id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): Only this environment. ## Responses ### 200 — The namespaces. Content type: `application/json` Type: `object` - `data` (array of NamespaceSummary) - `provider` (string) - `environment_id` (string) - `name` (string) - `applications` (integer) - `resources` (integer) - `unowned` (integer) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/namespaces' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/get-service-map.md # Get the service map `GET https://evohub.io/api/v1/catalog/map` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getServiceMap`. The applications not gone (at most 500, `truncated`) and the links between them: an edge that crosses from one application to another, or `shares_config` for two whose workloads read the same ConfigMap or Secret. `declared` are links people declared between services, kept apart from what was observed. Requires the `catalog:resource:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `environment_id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): Only this environment. - `namespace` (string) ## Responses ### 200 — The map. Content type: `application/json` Type: `object` - `data` (ServiceMap) - `applications` (array of Application) - `id` (string) - `environment_id` (string) - `namespace` (string) - `provider` (string) - `key_source` (string): What grouped it, such as an Argo CD application or a Helm release. - `key` (string) - `name` (string) - `component_id` (string | null) - `component_bound_by` (string | null) - `owner_hint` (string | null): The team its `evohub.io/team`, `team` or `owner` label names. - `status` (string, one of `live`, `missing`, `gone`) - `members` (integer) - `by_status` (object) - Other keys: integer - `kinds` (object) - Other keys: integer - `images` (array of string) - `health` (Health, one of `healthy`, `degraded`, `down`, `idle`, `unknown`) - `ready` (integer) - `desired` (integer) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) - `links` (array of object) - `from` (string) - `to` (string) - `type` (string, example `shares_config`) - `count` (integer) - `declared` (array of object) - `from_component_id` (string) - `to_component_id` (string) - `type` (string) - `truncated` (boolean) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/map' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/get-drift.md # Get drift `GET https://evohub.io/api/v1/catalog/drift` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getDrift`. Where what was declared and what runs disagree. Each list is capped at 100 (200 for `exposed`) with a `truncated` flag: - `unowned`: live resources with no service, per environment and kind - `undeployed`: services with no live resource; retired ones are left out - `environment_diff`: per service running in more than one environment, the kinds and names it runs in some and not others, compared without the namespace - `version_diff`: per service, the workloads running different images in one environment than in another - `vanished`: missing and gone resources, most recent first - `not_ready`: live workloads with fewer pods ready than wanted, or containers failing - `exposed`: cloud resources whose security groups open an admin port (SSH, RDP, databases) to the whole internet `environment_diff` and `version_diff` always compare every environment, whatever `environment_id` says. Requires the `catalog:component:read` and `catalog:resource:read` scopes. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `environment_id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): Only this environment. ## Responses ### 200 — The drift report. Content type: `application/json` Type: `object` - `data` (Drift) - `unowned` (object) - `total` (integer) - `by_kind` (array of object) - `environment_id` (string) - `provider` (string) - `kind` (string) - `category` (string) - `live` (integer) - `unowned` (integer) - `truncated` (boolean) - `undeployed` (object) - `total` (integer) - `components` (array of object) - `id` (string) - `slug` (string) - `display_name` (string) - `owner_team_id` (string | null) - `lifecycle` (Lifecycle, one of `experimental`, `production`, `deprecated`, `retired`) - `truncated` (boolean) - `environment_diff` (object) - `components` (array of object) - `component_id` (string) - `slug` (string) - `environments` (array of string) - `differences` (array of object) - `kind` (string) - `name` (string) - `present_in` (array of string) - `missing_from` (array of string) - `truncated` (boolean) - `version_diff` (object) - `workloads` (array of object) - `component_id` (string) - `slug` (string) - `kind` (string) - `name` (string) - `environments` (array of object) - `environment_id` (string) - `images` (array of string) - `truncated` (boolean) - `vanished` (object) - `missing` (integer) - `gone` (integer) - `resources` (array of object) - `id` (string) - `environment_id` (string) - `provider` (string) - `kind` (string) - `name` (string) - `namespace` (string) - `component_id` (string | null) - `status` (string) - `missing_since` (string (date-time)) - `truncated` (boolean) - `not_ready` (object) - `workloads` (array of object) - `resource_id` (string) - `environment_id` (string) - `kind` (string) - `name` (string) - `namespace` (string) - `ready` (integer) - `desired` (integer) - `restarts` (integer) - `reasons` (object) - Other keys: integer - `health` (Health, one of `healthy`, `degraded`, `down`, `idle`, `unknown`) - `truncated` (boolean) - `exposed` (object) - `resources` (array of object) - `resource_id` (string) - `environment_id` (string) - `provider` (string) - `kind` (string) - `name` (string) - `region` (string) - `ports` (array of ExposedPort) - `port` (integer, example `22`) - `service` (string, example `SSH`) - `protocol` (string) - `from` (string, example `0.0.0.0/0`) - `security_group_id` (string) - `truncated` (boolean) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/drift' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/list-tiers.md # List tiers `GET https://evohub.io/api/v1/catalog/tiers` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listTiers`. The organization's tiers in use, most critical first, each with the number of services in it. An organization starts with Tier 1 to Tier 4. Requires the `catalog:component:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `archived` (string, value `true`): `true` to include archived tiers. ## Responses ### 200 — The tiers, most critical first. Content type: `application/json` Type: `object` - `data` (array of Tier) - `id` (string) - `name` (string, example `Tier 1`) - `description` (string) - `rank` (integer): Its place in the order, 1 the most critical. - `color` (string, example `#d97706`) - `targets` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `version` (integer) - `archived_at` (string (date-time) | null) - `services` (integer): How many services are in it. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/tiers' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/create-tier.md # Create a tier `POST https://evohub.io/api/v1/catalog/tiers` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `createTier`. Adds a tier, last in the order. An organization may have at most 50 tiers in use. Requires the `catalog:component:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `TierRequest` - `name` (string, max length 60) - `description` (string, max length 1000) - `color` (string): Empty, or a hex colour such as `#d97706`. - `targets` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) ## Responses ### 201 — The tier. Content type: `application/json` Type: `object` - `data` (Tier) - `id` (string) - `name` (string, example `Tier 1`) - `description` (string) - `rank` (integer): Its place in the order, 1 the most critical. - `color` (string, example `#d97706`) - `targets` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `version` (integer) - `archived_at` (string (date-time) | null) - `services` (integer): How many services are in it. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — Another tier already has this name (`TIER_NAME_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/tiers' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Customer facing", "color": "#d97706", "targets": { "response": { "high": 60, "critical": 15 }, "resolution": { "critical": 240 }, "availability": 99.9, "support_hours": { "kind": "24x7" }, "max_unowned_days": 7 }, "description": "Anything a customer sees or pays through" }' ``` --- Source: https://docs.evohub.io/catalog/reorder-tiers.md # Reorder tiers `PUT https://evohub.io/api/v1/catalog/tiers/order` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `reorderTiers`. Sets the order of the tiers in use, most critical first. Send every tier in use, once. A service's `tier` number follows the new order. Requires the `catalog:component:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `ids` (array of string, required) ## Responses ### 200 — The tiers, most critical first. Content type: `application/json` Type: `object` - `data` (array of Tier) - `id` (string) - `name` (string, example `Tier 1`) - `description` (string) - `rank` (integer): Its place in the order, 1 the most critical. - `color` (string, example `#d97706`) - `targets` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `version` (integer) - `archived_at` (string (date-time) | null) - `services` (integer): How many services are in it. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/catalog/tiers/order' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "ids": [ "tir_1f3a5c7e9b2d4f6a8c0e1b3d5f7a9c2e", "tir_8d0f2b4a6c8e1a3c5e7b9d1f3a5c7e9b" ] }' ``` --- Source: https://docs.evohub.io/catalog/update-tier.md # Change a tier `PATCH https://evohub.io/api/v1/catalog/tiers/{id}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `updateTier`. Changes only the fields sent. A change to the name, description or targets makes a new version of the tier, kept so a past month's SLA report reads the targets that applied then; a colour change does not. Requires the `catalog:component:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `tir_1f3a5c7e9b2d4f6a8c0e1b3d5f7a9c2e`): The tier's ID. ## Request body (required) Content type: `application/json` Type: `TierRequest` - `name` (string, max length 60) - `description` (string, max length 1000) - `color` (string): Empty, or a hex colour such as `#d97706`. - `targets` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) ## Responses ### 200 — The tier. Content type: `application/json` Type: `object` - `data` (Tier) - `id` (string) - `name` (string, example `Tier 1`) - `description` (string) - `rank` (integer): Its place in the order, 1 the most critical. - `color` (string, example `#d97706`) - `targets` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `version` (integer) - `archived_at` (string (date-time) | null) - `services` (integer): How many services are in it. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — Another tier already has this name (`TIER_NAME_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/catalog/tiers/tir_1f3a5c7e9b2d4f6a8c0e1b3d5f7a9c2e' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "targets": { "availability": 99.95 } }' ``` --- Source: https://docs.evohub.io/catalog/archive-tier.md # Archive a tier `POST https://evohub.io/api/v1/catalog/tiers/{id}/archive` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `archiveTier`. Takes the tier out of use: it can no longer be chosen, and the services already in it keep it. Tiers are archived, never deleted. Requires the `catalog:component:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `tir_1f3a5c7e9b2d4f6a8c0e1b3d5f7a9c2e`): The tier's ID. ## Responses ### 204 — Archived. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — No tier in use has this ID; an archived tier answers this too (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/tiers/tir_1f3a5c7e9b2d4f6a8c0e1b3d5f7a9c2e/archive' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/list-tier-versions.md # List a tier's versions `GET https://evohub.io/api/v1/catalog/tiers/{id}/versions` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listTierVersions`. Every version the tier had, newest first, with who changed it and when. Requires the `catalog:component:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `tir_1f3a5c7e9b2d4f6a8c0e1b3d5f7a9c2e`): The tier's ID. ## Responses ### 200 — The versions. Content type: `application/json` Type: `object` - `data` (array of TierVersion) - `version` (integer) - `name` (string) - `description` (string) - `targets` (SLATargets) - `availability` (number, example `99.9`): A percentage above 0 and at most 100, up to 3 decimals. - `response` (object): Minutes to acknowledge, per On-Call severity (`critical`, `high`, `medium`, `low`, `info`), 1 to 525600. - Other keys: integer - `resolution` (object): Minutes to resolve, per severity. - Other keys: integer - `support_hours` (object) - `kind` (string, required, one of `24x7`, `business`) - `timezone` (string, example `Europe/Istanbul`) - `days` (array of integer): ISO weekdays, 1 is Monday. - `from` (string, example `09:00`) - `to` (string, example `18:00`) - `max_unowned_days` (integer, min 0, max 3650) - `changed_by` (string) - `changed_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/tiers/tir_1f3a5c7e9b2d4f6a8c0e1b3d5f7a9c2e/versions' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/list-environments.md # List environments `GET https://evohub.io/api/v1/catalog/environments` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listEnvironments`. The organization's environments. Requires the `catalog:source:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The environments. Content type: `application/json` Type: `object` - `data` (array of Environment) - `id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`) - `org_id` (string) - `name` (string, example `eu-prod`) - `kind` (string, one of `prod`, `staging`, `dev`, `other`) - `description` (string) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/environments' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/create-environment.md # Create an environment `POST https://evohub.io/api/v1/catalog/environments` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `createEnvironment`. Declares an environment. `name` (at most 64 characters, unique in the organization regardless of case) and `kind` are required. Requires the `catalog:source:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `EnvironmentRequest` - `name` (string, max length 64) - `kind` (string, one of `prod`, `staging`, `dev`, `other`) - `description` (string, max length 500) ## Responses ### 201 — The environment. Content type: `application/json` Type: `object` - `data` (Environment) - `id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`) - `org_id` (string) - `name` (string, example `eu-prod`) - `kind` (string, one of `prod`, `staging`, `dev`, `other`) - `description` (string) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — Another one already has this name (`NAME_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/environments' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "kind": "prod", "name": "eu-prod", "description": "Production cluster in Frankfurt" }' ``` --- Source: https://docs.evohub.io/catalog/get-environment.md # Get an environment `GET https://evohub.io/api/v1/catalog/environments/{id}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getEnvironment`. Requires the `catalog:source:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): The environment's ID. ## Responses ### 200 — The environment. Content type: `application/json` Type: `object` - `data` (Environment) - `id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`) - `org_id` (string) - `name` (string, example `eu-prod`) - `kind` (string, one of `prod`, `staging`, `dev`, `other`) - `description` (string) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/environments/env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/delete-environment.md # Delete an environment `DELETE https://evohub.io/api/v1/catalog/environments/{id}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `deleteEnvironment`. Deletes an environment nothing reports into. While an agent or a cloud account still reports into it, the answer is 409 `ENVIRONMENT_IN_USE`. Requires the `catalog:source:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): The environment's ID. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — Something still reports into it (`ENVIRONMENT_IN_USE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/catalog/environments/env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/update-environment.md # Change an environment `PATCH https://evohub.io/api/v1/catalog/environments/{id}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `updateEnvironment`. Changes only the fields sent. Requires the `catalog:source:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): The environment's ID. ## Request body (required) Content type: `application/json` Type: `EnvironmentRequest` - `name` (string, max length 64) - `kind` (string, one of `prod`, `staging`, `dev`, `other`) - `description` (string, max length 500) ## Responses ### 200 — The environment. Content type: `application/json` Type: `object` - `data` (Environment) - `id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`) - `org_id` (string) - `name` (string, example `eu-prod`) - `kind` (string, one of `prod`, `staging`, `dev`, `other`) - `description` (string) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — Another one already has this name (`NAME_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/catalog/environments/env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "description": "Production cluster in Frankfurt, since October" }' ``` --- Source: https://docs.evohub.io/catalog/list-sources.md # List sources `GET https://evohub.io/api/v1/catalog/sources` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listSources`. Where the organization's observations come from: one source per enrolled agent (`agent`) and per cloud account (`cloud`). Sources are not created or deleted here: an agent's appears when it first reports and is retired by revoking the agent; a cloud account's comes and goes with the account. Requires the `catalog:source:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `environment_id` (string, example `env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b`): Only this environment. - `kind` (string, one of `agent`, `cloud`) - `status` (string, one of `active`, `disabled`, `revoked`) ## Responses ### 200 — The sources. Content type: `application/json` Type: `object` - `data` (array of Source) - `id` (string) - `org_id` (string) - `kind` (string, one of `agent`, `cloud`) - `environment_id` (string | null) - `agent_id` (string | null) - `name` (string) - `status` (string, one of `active`, `disabled`, `revoked`) - `last_seen_at` (string (date-time) | null) - `agent_version` (string): What the agent last said it runs; empty for a cloud account. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/sources' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/get-source.md # Get a source `GET https://evohub.io/api/v1/catalog/sources/{id}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getSource`. Requires the `catalog:source:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `src_4a6c8e0b2d4f6a8c1e3b5d7f9a2c4e6b`): The source's ID. ## Responses ### 200 — The source. Content type: `application/json` Type: `object` - `data` (Source) - `id` (string) - `org_id` (string) - `kind` (string, one of `agent`, `cloud`) - `environment_id` (string | null) - `agent_id` (string | null) - `name` (string) - `status` (string, one of `active`, `disabled`, `revoked`) - `last_seen_at` (string (date-time) | null) - `agent_version` (string): What the agent last said it runs; empty for a cloud account. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/sources/src_4a6c8e0b2d4f6a8c1e3b5d7f9a2c4e6b' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/update-source.md # Rename, pause or resume a source `PATCH https://evohub.io/api/v1/catalog/sources/{id}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `updateSource`. Renames a source, or pauses (`disabled`) and resumes (`active`) it. A paused agent's reports are refused until it is resumed. `revoked` cannot be set here: revoke the agent under **Organization → Agents**. A revoked source answers 409 `SOURCE_REVOKED`. Requires the `catalog:source:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `src_4a6c8e0b2d4f6a8c1e3b5d7f9a2c4e6b`): The source's ID. ## Request body (required) Content type: `application/json` Type: `object` - `name` (string, max length 128) - `status` (string, one of `active`, `disabled`) ## Responses ### 200 — The source. Content type: `application/json` Type: `object` - `data` (Source) - `id` (string) - `org_id` (string) - `kind` (string, one of `agent`, `cloud`) - `environment_id` (string | null) - `agent_id` (string | null) - `name` (string) - `status` (string, one of `active`, `disabled`, `revoked`) - `last_seen_at` (string (date-time) | null) - `agent_version` (string): What the agent last said it runs; empty for a cloud account. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The source is revoked (`SOURCE_REVOKED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/catalog/sources/src_4a6c8e0b2d4f6a8c1e3b5d7f9a2c4e6b' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "status": "disabled" }' ``` --- Source: https://docs.evohub.io/catalog/get-cloud-setup.md # Get what a connection needs `GET https://evohub.io/api/v1/catalog/connections/setup` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getCloudSetup`. Which providers can be connected, and the principal each customer account grants read access to: the role ARN an AWS role trusts, an application's client id (Azure), a service account (GCP). For Azure and GCP, `tag` is the tag or label that marks a subscription or project for a connection. Requires the `identity:cloudaccount:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — What a connection needs. Content type: `application/json` Type: `object` - `data` (CloudSetup) - `available` (boolean): Whether any provider can be connected. - `principal_arn` (string): The AWS principal a role trusts. - `providers` (array of object) - `provider` (string, one of `aws`, `azure`, `gcp`, `vmind`) - `available` (boolean) - `principal` (string) - `tag` (string, example `evohub-connection`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/connections/setup' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/list-connections.md # List cloud accounts `GET https://evohub.io/api/v1/catalog/connections` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `listConnections`. The organization's cloud accounts. `external_id` is empty for a key without `identity:cloudaccount:write`. Requires the `identity:cloudaccount:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The accounts. Content type: `application/json` Type: `object` - `data` (array of CloudConnection) - `id` (string, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`) - `source_id` (string) - `environment_id` (string) - `provider` (string, one of `aws`, `azure`, `gcp`, `vmind`) - `name` (string) - `role_arn` (string) - `tenant_id` (string) - `credential_id` (string) - `has_secret` (boolean) - `external_id` (string): Ours, random and unique. Empty without `identity:cloudaccount:write`. - `regions` (array of string): Empty means every region the account has enabled. - `account_id` (string) - `status` (string, one of `pending`, `ok`, `error`, `paused`) - `last_error` (string) - `denied` (array of string): Services the access did not let it read on the last read. Not a failure. - `last_polled_at` (string (date-time) | null) - `next_poll_at` (string (date-time)) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/connections' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/create-connection.md # Connect a cloud account `POST https://evohub.io/api/v1/catalog/connections` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `createConnection`. Creates a connection reporting into one environment, in state `pending`. It is created before access is granted, so you have its `external_id` for the AWS role's trust policy, or for the Azure tag or GCP label. Then give the role ARN (AWS), directory id (Azure) or application credential (portvMind) with a `PATCH`. Leave `regions` empty to read every region the account has enabled. An organization may have at most 20 connections. Requires the `identity:cloudaccount:write` and `catalog:source:write` scopes. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `provider` (string, required, one of `aws`, `azure`, `gcp`, `vmind`) - `name` (string, required, max length 100) - `environment_id` (string, required) - `role_arn` (string): AWS: `arn:aws:iam:::role/EvoHub…`, no path. - `tenant_id` (string): Azure: the directory (tenant) id. - `regions` (array of string, max items 40) ## Responses ### 201 — The cloud account. Content type: `application/json` Type: `object` - `data` (CloudConnection) - `id` (string, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`) - `source_id` (string) - `environment_id` (string) - `provider` (string, one of `aws`, `azure`, `gcp`, `vmind`) - `name` (string) - `role_arn` (string) - `tenant_id` (string) - `credential_id` (string) - `has_secret` (boolean) - `external_id` (string): Ours, random and unique. Empty without `identity:cloudaccount:write`. - `regions` (array of string): Empty means every region the account has enabled. - `account_id` (string) - `status` (string, one of `pending`, `ok`, `error`, `paused`) - `last_error` (string) - `denied` (array of string): Services the access did not let it read on the last read. Not a failure. - `last_polled_at` (string (date-time) | null) - `next_poll_at` (string (date-time)) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — The name is taken (`NAME_TAKEN`), or the organization has 20 connections (`TOO_MANY_CONNECTIONS`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 503 — Cloud accounts for this provider cannot be connected on this EvoHub (`CLOUD_NOT_CONFIGURED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/connections' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Production account", "regions": [ "eu-central-1" ], "provider": "aws", "environment_id": "env_2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b" }' ``` --- Source: https://docs.evohub.io/catalog/get-connection.md # Get a cloud account `GET https://evohub.io/api/v1/catalog/connections/{id}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getConnection`. `external_id` is empty for a key without `identity:cloudaccount:write`. Requires the `identity:cloudaccount:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`): The cloud account's ID. ## Responses ### 200 — The cloud account. Content type: `application/json` Type: `object` - `data` (CloudConnection) - `id` (string, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`) - `source_id` (string) - `environment_id` (string) - `provider` (string, one of `aws`, `azure`, `gcp`, `vmind`) - `name` (string) - `role_arn` (string) - `tenant_id` (string) - `credential_id` (string) - `has_secret` (boolean) - `external_id` (string): Ours, random and unique. Empty without `identity:cloudaccount:write`. - `regions` (array of string): Empty means every region the account has enabled. - `account_id` (string) - `status` (string, one of `pending`, `ok`, `error`, `paused`) - `last_error` (string) - `denied` (array of string): Services the access did not let it read on the last read. Not a failure. - `last_polled_at` (string (date-time) | null) - `next_poll_at` (string (date-time)) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/connections/con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/delete-connection.md # Disconnect a cloud account `DELETE https://evohub.io/api/v1/catalog/connections/{id}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `deleteConnection`. Stops reading the account. Its source is revoked; what it reported stays in the catalog. Remove the role, application or service account access in your cloud as well. Requires the `identity:cloudaccount:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`): The cloud account's ID. ## Responses ### 204 — Disconnected. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/catalog/connections/con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/update-connection.md # Change a cloud account `PATCH https://evohub.io/api/v1/catalog/connections/{id}` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `updateConnection`. Changes the name, role ARN, directory id or regions, or gives a portvMind application credential (`credential_id` and `credential_secret`). The secret is stored encrypted and never returned; reads show `has_secret`. A changed role is read at once, but not within two minutes of the last read. Requires the `identity:cloudaccount:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`): The cloud account's ID. ## Request body (required) Content type: `application/json` Type: `object` - `name` (string, max length 100) - `role_arn` (string) - `tenant_id` (string) - `credential_id` (string) - `credential_secret` (string, max length 512, write-only) - `regions` (array of string, max items 40) ## Responses ### 200 — The cloud account. Content type: `application/json` Type: `object` - `data` (CloudConnection) - `id` (string, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`) - `source_id` (string) - `environment_id` (string) - `provider` (string, one of `aws`, `azure`, `gcp`, `vmind`) - `name` (string) - `role_arn` (string) - `tenant_id` (string) - `credential_id` (string) - `has_secret` (boolean) - `external_id` (string): Ours, random and unique. Empty without `identity:cloudaccount:write`. - `regions` (array of string): Empty means every region the account has enabled. - `account_id` (string) - `status` (string, one of `pending`, `ok`, `error`, `paused`) - `last_error` (string) - `denied` (array of string): Services the access did not let it read on the last read. Not a failure. - `last_polled_at` (string (date-time) | null) - `next_poll_at` (string (date-time)) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 409 — Another one already has this name (`NAME_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 503 — Cloud accounts for this provider cannot be connected on this EvoHub (`CLOUD_NOT_CONFIGURED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/catalog/connections/con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "role_arn": "arn:aws:iam::123456789012:role/EvoHubCatalogReadOnly" }' ``` --- Source: https://docs.evohub.io/catalog/pause-connection.md # Pause a cloud account `POST https://evohub.io/api/v1/catalog/connections/{id}/pause` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `pauseConnection`. Stops reading the account until it is resumed. Requires the `identity:cloudaccount:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`): The cloud account's ID. ## Responses ### 200 — The cloud account. Content type: `application/json` Type: `object` - `data` (CloudConnection) - `id` (string, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`) - `source_id` (string) - `environment_id` (string) - `provider` (string, one of `aws`, `azure`, `gcp`, `vmind`) - `name` (string) - `role_arn` (string) - `tenant_id` (string) - `credential_id` (string) - `has_secret` (boolean) - `external_id` (string): Ours, random and unique. Empty without `identity:cloudaccount:write`. - `regions` (array of string): Empty means every region the account has enabled. - `account_id` (string) - `status` (string, one of `pending`, `ok`, `error`, `paused`) - `last_error` (string) - `denied` (array of string): Services the access did not let it read on the last read. Not a failure. - `last_polled_at` (string (date-time) | null) - `next_poll_at` (string (date-time)) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/connections/con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b/pause' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/resume-connection.md # Resume a cloud account `POST https://evohub.io/api/v1/catalog/connections/{id}/resume` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `resumeConnection`. Reads the account again at once (but not within two minutes of the last read), then every 15 minutes. Requires the `identity:cloudaccount:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`): The cloud account's ID. ## Responses ### 200 — The cloud account. Content type: `application/json` Type: `object` - `data` (CloudConnection) - `id` (string, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`) - `source_id` (string) - `environment_id` (string) - `provider` (string, one of `aws`, `azure`, `gcp`, `vmind`) - `name` (string) - `role_arn` (string) - `tenant_id` (string) - `credential_id` (string) - `has_secret` (boolean) - `external_id` (string): Ours, random and unique. Empty without `identity:cloudaccount:write`. - `regions` (array of string): Empty means every region the account has enabled. - `account_id` (string) - `status` (string, one of `pending`, `ok`, `error`, `paused`) - `last_error` (string) - `denied` (array of string): Services the access did not let it read on the last read. Not a failure. - `last_polled_at` (string (date-time) | null) - `next_poll_at` (string (date-time)) - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/connections/con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b/resume' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/check-connection.md # Check a cloud account `POST https://evohub.io/api/v1/catalog/connections/{id}/check` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `checkConnection`. Tries the access the way a read would: assumes the AWS role, or signs in and finds the marked subscriptions or projects. The answer is 200 either way: `{"ok": true, "account_id": "…"}` or `{"ok": false, "error": "…"}` with what the cloud said. Requires the `identity:cloudaccount:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`): The cloud account's ID. ## Responses ### 200 — What the cloud answered. Content type: `application/json` Type: `object` - `data` (object) - `ok` (boolean, required) - `account_id` (string) - `error` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 503 — Cloud accounts for this provider cannot be connected on this EvoHub (`CLOUD_NOT_CONFIGURED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/catalog/connections/con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b/check' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/get-connection-template.md # Download the AWS role template `GET https://evohub.io/api/v1/catalog/connections/{id}/template` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getConnectionTemplate`. The CloudFormation template that creates an AWS connection's read-only role (`evohub-catalog-role.yaml`): it trusts EvoHub's principal only with this connection's external id, and allows only the listing calls the catalog makes. AWS connections only; any other answers 404. Requires the `identity:cloudaccount:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`): The cloud account's ID. ## Responses ### 200 — The template, as an attachment. Content type: `application/yaml` Type: `string` ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 503 — Cloud accounts for this provider cannot be connected on this EvoHub (`CLOUD_NOT_CONFIGURED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/connections/con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b/template' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/get-connection-architecture.md # Get an account's architecture `GET https://evohub.io/api/v1/catalog/connections/{id}/architecture` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `getConnectionArchitecture`. One region of the account laid out from what it reported: each network (VPC) with its internet gateway, peerings and subnets (public or private, and the NAT gateway a private one leaves through), what runs in each subnet, what spans the network, what sits outside any network, the security groups, and the links between them. A node lists the admin ports its security groups open to the whole internet. Requires the `identity:cloudaccount:read` and `catalog:resource:read` scopes. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b`): The cloud account's ID. ## Query parameters - `region` (string, example `eu-central-1`): The region to lay out. `regions` in the answer lists those with resources. ## Responses ### 200 — The architecture. Content type: `application/json` Type: `object` - `data` (Architecture) - `connection_id` (string) - `provider` (string) - `account_id` (string) - `region` (string) - `regions` (array of object) - `vpcs` (array of object): Each network with its internet gateway, subnets, what spans it and its peerings. - `outside` (array of object): What sits in no network, such as buckets. - `security_groups` (array of object) - `links` (array of object): `traffic`, `egress`, `internet` and `allows` links between nodes. - `links_truncated` (boolean) ### 400 — The request is not valid: a field or filter value is wrong (`VALIDATION_ERROR`, with a sentence saying what), the body is not the expected JSON or carries an unknown field (`INVALID_BODY`), the cursor is not one this list gave (`INVALID_CURSOR`), a binding names a service that does not exist (`UNKNOWN_COMPONENT`), or a `PATCH` tries to change a slug (`SLUG_IMMUTABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 404 — Nothing with this ID (or slug) exists in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/connections/con_7d9f1b3a5c7e9a2c4e6b8d0f2a4c6e8b/architecture' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/catalog/export-inventory.md # Export the inventory `GET https://evohub.io/api/v1/catalog/exports/inventory` Part of the [Catalog API](https://docs.evohub.io/catalog.md) reference · operationId `exportInventory`. Every asset not gone, with its application, service, owner team, tier, effective availability target and RACI, as of `generated_at`. At most 50,000 rows; `truncated` says when there were more. Teams and people are their IDs. Requires the `catalog:component:read` and `catalog:resource:read` scopes. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The inventory. Content type: `application/json` Type: `object` - `data` (Inventory) - `generated_at` (string (date-time)) - `truncated` (boolean) - `assets` (array of object) - `resource_id` (string) - `provider` (string) - `kind` (string) - `name` (string) - `namespace` (string) - `environment_id` (string) - `status` (string) - `first_seen_at` (string (date-time)) - `last_seen_at` (string (date-time)) - `application_id` (string | null) - `application_name` (string | null) - `component_id` (string | null) - `component_slug` (string | null) - `component_name` (string | null) - `owner_team_id` (string | null) - `tier_id` (string | null) - `tier_name` (string | null) - `availability_target` (number | null) - `responsibilities` (array of Responsibility) - `role` (string, required, one of `responsible`, `accountable`, `consulted`, `informed`) - `team_id` (string | null) - `user_id` (string | null) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 403 — The key lacks a scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/catalog/exports/inventory' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail.md # EvoTrail API Version 1.0 · OpenAPI 3.1.0 EvoTrail is the organization's audit trail, usage and metrics across every EvoHub product, in one place. This API reads it: search and export the audit trail, see how much each product is used, and read each product's metrics through one prefix. Everything here is read-only and free. **Authentication.** Send an API key as `Authorization: Bearer evohub_…` or `X-API-Key: evohub_…`. A key works in one organization. See [API keys and scopes](/api-keys-and-scopes) and [Errors](/errors). **Permissions, two layers.** Each operation names the EvoTrail permission it needs (`x-evohub-permission`): `evotrail:audit:read` for the audit trail, `evotrail:metrics:read` for usage, overview and metrics. On top of that, each product's rows or numbers need that product's own permission: an audit row needs the product's `*:audit:read` (for example `oncall:audit:read`), a product's usage and metrics need one of its read permissions (for example `uptime:monitor:read`). Products the key cannot read are left out of the answer; asking only for those is **403 `FORBIDDEN`**. A refusal is never 401. A key is always held to its scopes, even one made by an administrator. API keys never count as owners or administrators, so the per-person views below are not available to a key. **Retention is fixed** for every organization: audit events 365 days, activity events 90 days, IP addresses and user agents 90 days. **Ranges.** `from` and `to` are a UTC date (`YYYY-MM-DD`, `to` inclusive) or an RFC 3339 timestamp. By default the last 30 days; at most 366 days. **Responses.** Success is `{"data": …, "success": true}`. Errors are `{"error": {"code", "message", "request_id"}, "success": false}`; `request_id` is the request's id, also sent in the `X-Request-ID` header. Times are RFC 3339 in UTC. ## Servers - `https://evohub.io` ## Authentication - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Audit trail Every product's audit and activity events, searched, counted and exported. - [GET /api/v1/evotrail/audit](https://docs.evohub.io/evotrail/search-audit-trail.md): Search the audit trail - [GET /api/v1/evotrail/audit/facets](https://docs.evohub.io/evotrail/get-audit-facets.md): Count the audit trail by dimension - [GET /api/v1/evotrail/audit/export.csv](https://docs.evohub.io/evotrail/export-audit-trail.md): Export the audit trail as CSV - [GET /api/v1/evotrail/audit/{id}](https://docs.evohub.io/evotrail/get-audit-event.md): Get one audit event ## Usage How much each product is used, by whom and by which team. - [GET /api/v1/evotrail/usage](https://docs.evohub.io/evotrail/get-usage.md): Get usage by product - [GET /api/v1/evotrail/usage/people](https://docs.evohub.io/evotrail/get-usage-by-person.md): Get the most active people - [GET /api/v1/evotrail/overview](https://docs.evohub.io/evotrail/get-overview.md): Get the overview ## Product metrics Each product's own analytics, read through EvoTrail. - [GET /api/v1/evotrail/metrics/oncall/alerts](https://docs.evohub.io/evotrail/get-oncall-alert-metrics.md): On-Call alert analytics - [GET /api/v1/evotrail/metrics/oncall/incidents](https://docs.evohub.io/evotrail/get-oncall-incident-metrics.md): On-Call incident analytics - [GET /api/v1/evotrail/metrics/uptime](https://docs.evohub.io/evotrail/get-uptime-metrics.md): Uptime analytics - [GET /api/v1/evotrail/metrics/status](https://docs.evohub.io/evotrail/get-status-metrics.md): Status analytics across pages - [GET /api/v1/evotrail/metrics/status/pages/{id}](https://docs.evohub.io/evotrail/get-status-page-metrics.md): One status page's visitor analytics - [GET /api/v1/evotrail/metrics/status/pages/{id}/hourly](https://docs.evohub.io/evotrail/get-status-page-hourly-metrics.md): One status page's views by the hour - [GET /api/v1/evotrail/metrics/docs](https://docs.evohub.io/evotrail/get-docs-metrics.md): Docs analytics across sites - [GET /api/v1/evotrail/metrics/docs/sites/{id}](https://docs.evohub.io/evotrail/get-docs-site-metrics.md): One docs site's analytics - [GET /api/v1/evotrail/metrics/docs/sites/{id}/search](https://docs.evohub.io/evotrail/get-docs-site-search-metrics.md): One docs site's search analytics - [GET /api/v1/evotrail/metrics/docs/sites/{id}/ai](https://docs.evohub.io/evotrail/get-docs-site-ai-metrics.md): One docs site's AI analytics - [GET /api/v1/evotrail/metrics/docs/sites/{id}/gaps](https://docs.evohub.io/evotrail/get-docs-site-gap-metrics.md): One docs site's gap analytics - [GET /api/v1/evotrail/metrics/changelog](https://docs.evohub.io/evotrail/get-changelog-metrics.md): Changelog analytics across sites - [GET /api/v1/evotrail/metrics/changelog/sites/{id}](https://docs.evohub.io/evotrail/get-changelog-site-metrics.md): One changelog's analytics - [GET /api/v1/evotrail/metrics/board](https://docs.evohub.io/evotrail/get-board-metrics.md): Board analytics - [GET /api/v1/evotrail/metrics/retro](https://docs.evohub.io/evotrail/get-retro-metrics.md): Retro analytics - [GET /api/v1/evotrail/metrics/retro/stats](https://docs.evohub.io/evotrail/get-retro-stats.md): Retro statistics - [GET /api/v1/evotrail/metrics/billing](https://docs.evohub.io/evotrail/get-billing-metrics.md): Billing summary - [GET /api/v1/evotrail/metrics/billing/usage](https://docs.evohub.io/evotrail/get-billing-usage-metrics.md): Billing usage - [GET /api/v1/evotrail/metrics/billing/credit](https://docs.evohub.io/evotrail/get-billing-credit-metrics.md): Billing credit - [GET /api/v1/evotrail/metrics/billing/statements](https://docs.evohub.io/evotrail/get-billing-statement-metrics.md): Billing statements ## Settings EvoTrail's fixed retention. - [GET /api/v1/evotrail/settings](https://docs.evohub.io/evotrail/get-evotrail-settings.md): Get the retention periods --- Source: https://docs.evohub.io/evotrail/search-audit-trail.md # Search the audit trail `GET https://evohub.io/api/v1/evotrail/audit` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `searchAuditTrail`. Events from every product the key may read, newest first. Filters combine. Paged by cursor: pass `next_cursor` back as `cursor` while `has_more` is true. The list rows leave out `before`, `after` and `metadata`; read one event for those. **Permission (API-key scope):** `evotrail:audit:read`. Each product's rows also need that product's `*:audit:read`; Catalog's rows are for owners and administrators only. ## Authorization Any one of: - `bearerAuth` (evotrail:audit:read) - `apiKeyHeader` (evotrail:audit:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `product` (array of Product): Only these products; repeat it (`product=oncall&product=uptime`) or comma-separate. Products the key cannot read are dropped. Default: every product it can read. - `actor_id` (string, max length 200): Who did it: a user, API key or agent id. - `actor_type` (string, one of `user`, `api_key`, `agent`, `system`, `evohub_staff`) - `action` (string, max length 200, pattern `^[a-z0-9_.]+\*?$`, example `member.*`): An exact action, or a prefix ending in `*` (`alert.*`). Lower case, digits, `_` and `.`. - `resource_type` (string, max length 200) - `resource_id` (string, max length 200) - `team_id` (string, max length 200): Events on resources of this team. - `request_id` (string, max length 200): Every event one API request caused. - `ip` (string): An IPv4 or IPv6 address. - `class` (string, one of `audit`, `activity`) - `from` (string, example `2026-09-10`): `YYYY-MM-DD` (UTC) or RFC 3339. Default 30 days before `to`. - `to` (string, example `2026-10-09`): `YYYY-MM-DD` (inclusive) or RFC 3339. Default now. - `limit` (integer, default `50`, min 1, max 200) - `cursor` (string): `next_cursor` from the previous page. ## Responses ### 200 — A page of events. Content type: `application/json` Type: `object` - `data` (object, required) - `items` (array of AuditEvent) - `id` (string (uuid), required) - `event_id` (string, required): The producing product's own id for the event. - `source` (string, required): The service that reported it. - `product` (Product, required, one of `organization`, `oncall`, `uptime`, `status`, `docs`, `changelog`, `board`, `retro`, `support`, `catalog`, `evotrail`) - `team_id` (string | null): The team the resource belongs to. - `actor` (object, required) - `type` (string, one of `user`, `api_key`, `agent`, `system`, `evohub_staff`) - `id` (string | null) - `name` (string | null) - `teams` (array of string | null) - `action` (string, required): `.`, for example `alert.acknowledged` or `member.role_changed`. - `class` (string, required, one of `audit`, `activity`): `audit` is a change to configuration or access (kept 365 days); `activity` is day-to-day work (kept 90 days). - `resource` (object, required) - `type` (string | null) - `id` (string | null) - `name` (string | null) - `request_id` (string | null) - `ip` (string | null): Kept 90 days. - `user_agent` (string | null): Kept 90 days. - `occurred_at` (string (date-time), required) - `has_details` (boolean, required): Whether `before`, `after` or `metadata` exist; read the single event to get them. - `before` (any): The resource before the change (single-event read only). Secrets are masked by the producer. - `after` (any): The resource after the change (single-event read only). - `metadata` (any): Anything else the product recorded (single-event read only). - `next_cursor` (string): Absent on the last page. - `has_more` (boolean) - `products` (array of Product): The products searched. - `from` (string (date-time)) - `to` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — A range or filter is not valid (`VALIDATION_ERROR`); the message names it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks the EvoTrail scope, or may read none of the products asked for (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/audit' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-audit-facets.md # Count the audit trail by dimension `GET https://evohub.io/api/v1/evotrail/audit/facets` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getAuditFacets`. How many events match the filters, counted by product, action, actor, actor type, resource type, class and team. Each dimension is counted without its own filter, so the other values stay visible. Actions, actors, resource types and teams list the top 50. **Permission (API-key scope):** `evotrail:audit:read`. Each product's rows also need that product's `*:audit:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:audit:read) - `apiKeyHeader` (evotrail:audit:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `product` (array of Product): Only these products; repeat it (`product=oncall&product=uptime`) or comma-separate. Products the key cannot read are dropped. Default: every product it can read. - `actor_id` (string, max length 200): Who did it: a user, API key or agent id. - `actor_type` (string, one of `user`, `api_key`, `agent`, `system`, `evohub_staff`) - `action` (string, max length 200, pattern `^[a-z0-9_.]+\*?$`, example `member.*`): An exact action, or a prefix ending in `*` (`alert.*`). Lower case, digits, `_` and `.`. - `resource_type` (string, max length 200) - `resource_id` (string, max length 200) - `team_id` (string, max length 200): Events on resources of this team. - `request_id` (string, max length 200): Every event one API request caused. - `ip` (string): An IPv4 or IPv6 address. - `class` (string, one of `audit`, `activity`) - `from` (string, example `2026-09-10`): `YYYY-MM-DD` (UTC) or RFC 3339. Default 30 days before `to`. - `to` (string, example `2026-10-09`): `YYYY-MM-DD` (inclusive) or RFC 3339. Default now. ## Responses ### 200 — Counts per dimension. Content type: `application/json` Type: `object` - `data` (object, required) - `products` (array of Facet) - `value` (string) - `label` (string): A display name, for actors. - `count` (integer) - `actions` (array of Facet) - `value` (string) - `label` (string): A display name, for actors. - `count` (integer) - `actors` (array of Facet) - `value` (string) - `label` (string): A display name, for actors. - `count` (integer) - `actor_types` (array of Facet) - `value` (string) - `label` (string): A display name, for actors. - `count` (integer) - `resource_types` (array of Facet) - `value` (string) - `label` (string): A display name, for actors. - `count` (integer) - `classes` (array of Facet) - `value` (string) - `label` (string): A display name, for actors. - `count` (integer) - `teams` (array of Facet) - `value` (string) - `label` (string): A display name, for actors. - `count` (integer) - Other keys: array of Facet - `success` (boolean, required, value `true`) ### 400 — A range or filter is not valid (`VALIDATION_ERROR`); the message names it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks the EvoTrail scope, or may read none of the products asked for (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/audit/facets' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/export-audit-trail.md # Export the audit trail as CSV `GET https://evohub.io/api/v1/evotrail/audit/export.csv` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `exportAuditTrail`. Every event the filters match, newest first, streamed as CSV with the columns `occurred_at, product, action, class, actor_type, actor_id, actor_name, resource_type, resource_id, resource_name, team_id, request_id, ip, user_agent, event_id, id`. More than 100,000 matching events is refused: narrow the range or filters. A cell that starts with `=`, `+`, `-`, `@`, a tab or a carriage return is prefixed with `'`. The export is itself recorded in the trail as `evotrail` `audit.exported`. **Permission (API-key scope):** `evotrail:audit:read`. Each product's rows also need that product's `*:audit:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:audit:read) - `apiKeyHeader` (evotrail:audit:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `product` (array of Product): Only these products; repeat it (`product=oncall&product=uptime`) or comma-separate. Products the key cannot read are dropped. Default: every product it can read. - `actor_id` (string, max length 200): Who did it: a user, API key or agent id. - `actor_type` (string, one of `user`, `api_key`, `agent`, `system`, `evohub_staff`) - `action` (string, max length 200, pattern `^[a-z0-9_.]+\*?$`, example `member.*`): An exact action, or a prefix ending in `*` (`alert.*`). Lower case, digits, `_` and `.`. - `resource_type` (string, max length 200) - `resource_id` (string, max length 200) - `team_id` (string, max length 200): Events on resources of this team. - `request_id` (string, max length 200): Every event one API request caused. - `ip` (string): An IPv4 or IPv6 address. - `class` (string, one of `audit`, `activity`) - `from` (string, example `2026-09-10`): `YYYY-MM-DD` (UTC) or RFC 3339. Default 30 days before `to`. - `to` (string, example `2026-10-09`): `YYYY-MM-DD` (inclusive) or RFC 3339. Default now. ## Responses ### 200 — The CSV, as an attachment named `evotrail-audit--.csv`. Content type: `text/csv` Type: `string` ### 400 — A filter is not valid (`VALIDATION_ERROR`), or more than 100,000 events match (`EXPORT_TOO_LARGE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks the EvoTrail scope, or may read none of the products asked for (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/audit/export.csv' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-audit-event.md # Get one audit event `GET https://evohub.io/api/v1/evotrail/audit/{id}` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getAuditEvent`. One event with `before`, `after` and `metadata`. An event of a product the key may not read answers 404, as if it did not exist. **Permission (API-key scope):** `evotrail:audit:read`. The event's product also needs its `*:audit:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:audit:read) - `apiKeyHeader` (evotrail:audit:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, max length 200): The id named by the path: an audit event's id (a UUID) under `/audit`, a status page's or a docs or changelog site's id under `/metrics`. ## Responses ### 200 — The event. Content type: `application/json` Type: `object` - `data` (AuditEvent, required) - `id` (string (uuid), required) - `event_id` (string, required): The producing product's own id for the event. - `source` (string, required): The service that reported it. - `product` (Product, required, one of `organization`, `oncall`, `uptime`, `status`, `docs`, `changelog`, `board`, `retro`, `support`, `catalog`, `evotrail`) - `team_id` (string | null): The team the resource belongs to. - `actor` (object, required) - `type` (string, one of `user`, `api_key`, `agent`, `system`, `evohub_staff`) - `id` (string | null) - `name` (string | null) - `teams` (array of string | null) - `action` (string, required): `.`, for example `alert.acknowledged` or `member.role_changed`. - `class` (string, required, one of `audit`, `activity`): `audit` is a change to configuration or access (kept 365 days); `activity` is day-to-day work (kept 90 days). - `resource` (object, required) - `type` (string | null) - `id` (string | null) - `name` (string | null) - `request_id` (string | null) - `ip` (string | null): Kept 90 days. - `user_agent` (string | null): Kept 90 days. - `occurred_at` (string (date-time), required) - `has_details` (boolean, required): Whether `before`, `after` or `metadata` exist; read the single event to get them. - `before` (any): The resource before the change (single-event read only). Secrets are masked by the producer. - `after` (any): The resource after the change (single-event read only). - `metadata` (any): Anything else the product recorded (single-event read only). - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks the EvoTrail scope, or may read none of the products asked for (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — No such event, or one the key may not read (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/audit/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-usage.md # Get usage by product `GET https://evohub.io/api/v1/evotrail/usage` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getUsage`. For each product the key may read: actions and active people in the range, compared with the previous period of the same length, a series (hourly for a range of 48 hours or less with no team or person filter, daily otherwise), and the top 10 actions and teams. Every number carries a `drill`: the audit-trail filters that list the events behind it. **Permission (API-key scope):** `evotrail:metrics:read`. Each product also needs one of its read permissions. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `product` (array of Product): Only these products; repeat it (`product=oncall&product=uptime`) or comma-separate. Products the key cannot read are dropped. Default: every product it can read. - `team_id` (string): Only events on this team's resources. - `actor_id` (string): Only this person's events. Owners and administrators only: an API key always gets 403. - `from` (string, example `2026-09-10`): `YYYY-MM-DD` (UTC) or RFC 3339. Default 30 days before `to`. - `to` (string, example `2026-10-09`): `YYYY-MM-DD` (inclusive) or RFC 3339. Default now. ## Responses ### 200 — Usage. Content type: `application/json` Type: `object` - `data` (UsageView, required) - `from` (string (date-time)) - `to` (string (date-time)) - `previous_from` (string (date-time)): Start of the previous period of the same length, which deltas compare with. - `granularity` (string, one of `hour`, `day`) - `totals` (object) - `actions` (integer) - `previous_actions` (integer) - `delta_pct` (number | null): `null` when the previous period had none. - `active_people` (integer) - `previous_active_people` (integer) - `drill` (Drill) - `product` (array of string) - `action` (string) - `team_id` (string) - `actor_id` (string) - `from` (string (date-time), required) - `to` (string (date-time), required) - `products` (array of object) - `product` (Product, one of `organization`, `oncall`, `uptime`, `status`, `docs`, `changelog`, `board`, `retro`, `support`, `catalog`, `evotrail`) - `actions` (integer) - `previous_actions` (integer) - `delta_pct` (number | null) - `active_people` (integer) - `last_active_day` (string (date) | null) - `series` (array of Point) - `t` (string (date-time)) - `count` (integer) - `drill` (Drill) - `drill` (Drill) - `product` (array of string) - `action` (string) - `team_id` (string) - `actor_id` (string) - `from` (string (date-time), required) - `to` (string (date-time), required) - `top_actions` (array of object, max items 10) - `product` (string) - `action` (string) - `count` (integer) - `drill` (Drill) - `product` (array of string) - `action` (string) - `team_id` (string) - `actor_id` (string) - `from` (string (date-time), required) - `to` (string (date-time), required) - `top_teams` (array of object, max items 10) - `team_id` (string) - `actions` (integer) - `active_people` (integer) - `drill` (Drill) - `product` (array of string) - `action` (string) - `team_id` (string) - `actor_id` (string) - `from` (string (date-time), required) - `to` (string (date-time), required) - `quiet_products` (array of object): Readable products with no action in the range. - `product` (string) - `last_active_day` (string (date) | null) - `success` (boolean, required, value `true`) ### 400 — A range or filter is not valid (`VALIDATION_ERROR`); the message names it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks the scope, may read none of the products asked for, or used `actor_id` (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/usage' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-usage-by-person.md # Get the most active people `GET https://evohub.io/api/v1/evotrail/usage/people` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getUsageByPerson`. The 50 most active people in the range with their actions per product. This is for organization owners and administrators signed in to the console only: no permission opens it, so an API key always gets **403**. **Permission (API-key scope):** `evotrail:metrics:read`. Not enough on its own: the caller must also be an organization owner or administrator, which an API key never is. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `product` (array of Product): Only these products; repeat it (`product=oncall&product=uptime`) or comma-separate. Products the key cannot read are dropped. Default: every product it can read. - `team_id` (string) - `from` (string, example `2026-09-10`): `YYYY-MM-DD` (UTC) or RFC 3339. Default 30 days before `to`. - `to` (string, example `2026-10-09`): `YYYY-MM-DD` (inclusive) or RFC 3339. Default now. ## Responses ### 200 — The people. Content type: `application/json` Type: `object` - `data` (object, required) - `from` (string (date-time)) - `to` (string (date-time)) - `people` (array of object) - `actor_id` (string) - `name` (string | null) - `actions` (integer) - `by_product` (object) - Other keys: integer - `last_active_day` (string (date)) - `drill` (Drill) - `product` (array of string) - `action` (string) - `team_id` (string) - `actor_id` (string) - `from` (string (date-time), required) - `to` (string (date-time), required) - `success` (boolean, required, value `true`) ### 400 — A range or filter is not valid (`VALIDATION_ERROR`); the message names it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — Always, for an API key: only owners and administrators may see per-person usage (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/usage/people' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-overview.md # Get the overview `GET https://evohub.io/api/v1/evotrail/overview` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getOverview`. One line per product the key may read (Billing included): actions and active people against the previous period, the day it was last used, and every metric the product reported, summed over the range (for example `page_views` or `incidents_published` for Status). `feeds` says how fresh EvoTrail's copy of each product's events and metrics is. **Permission (API-key scope):** `evotrail:metrics:read`. Each product also needs one of its read permissions. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `from` (string, example `2026-09-10`): `YYYY-MM-DD` (UTC) or RFC 3339. Default 30 days before `to`. - `to` (string, example `2026-10-09`): `YYYY-MM-DD` (inclusive) or RFC 3339. Default now. ## Responses ### 200 — The overview. Content type: `application/json` Type: `object` - `data` (object, required) - `from` (string (date-time)) - `to` (string (date-time)) - `previous_from` (string (date-time)) - `products` (array of object) - `product` (string) - `actions` (integer) - `previous_actions` (integer) - `delta_pct` (number | null) - `active_people` (integer) - `last_active_day` (string (date) | null) - `metrics` (object) - Other keys: number - `previous_metrics` (object) - Other keys: number - `drill` (Drill) - `product` (array of string) - `action` (string) - `team_id` (string) - `actor_id` (string) - `from` (string (date-time), required) - `to` (string (date-time), required) - `feeds` (array of object) - `source` (string) - `feed` (string, one of `audit`, `metrics`) - `caught_up_at` (string (date-time) | null) - `last_success_at` (string (date-time) | null) - `failing` (boolean) - `success` (boolean, required, value `true`) ### 400 — A range or filter is not valid (`VALIDATION_ERROR`); the message names it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks the scope, or may read no product (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/overview' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-oncall-alert-metrics.md # On-Call alert analytics `GET https://evohub.io/api/v1/evotrail/metrics/oncall/alerts` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getOncallAlertMetrics`. Read through EvoTrail: the same answer, and the same query parameters, as `GET /api/v1/alerts/analytics` in the On-Call API reference. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `oncall:alert:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/oncall/alerts' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-oncall-incident-metrics.md # On-Call incident analytics `GET https://evohub.io/api/v1/evotrail/metrics/oncall/incidents` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getOncallIncidentMetrics`. Read through EvoTrail: the same answer and query parameters as `GET /api/v1/incidents/analytics` in the On-Call API reference. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `oncall:incident:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/oncall/incidents' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-uptime-metrics.md # Uptime analytics `GET https://evohub.io/api/v1/evotrail/metrics/uptime` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getUptimeMetrics`. Read through EvoTrail: the same answer and query parameters as `GET /api/v1/uptime/analytics` in the Uptime API reference. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `uptime:monitor:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/uptime' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-status-metrics.md # Status analytics across pages `GET https://evohub.io/api/v1/evotrail/metrics/status` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getStatusMetrics`. Read through EvoTrail: the same answer and query parameters as `GET /api/v1/pages/analytics` in the Status API reference. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `status:page:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/status' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-status-page-metrics.md # One status page's visitor analytics `GET https://evohub.io/api/v1/evotrail/metrics/status/pages/{id}` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getStatusPageMetrics`. Read through EvoTrail: the same answer and query parameters as `GET /api/v1/pages/{pageID}/analytics` in the Status API reference; `id` is the page's id. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `status:page:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, max length 200): The id named by the path: an audit event's id (a UUID) under `/audit`, a status page's or a docs or changelog site's id under `/metrics`. ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/status/pages/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-status-page-hourly-metrics.md # One status page's views by the hour `GET https://evohub.io/api/v1/evotrail/metrics/status/pages/{id}/hourly` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getStatusPageHourlyMetrics`. Read through EvoTrail: the same answer and query parameters as `GET /api/v1/pages/{pageID}/analytics/hourly` in the Status API reference. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `status:page:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, max length 200): The id named by the path: an audit event's id (a UUID) under `/audit`, a status page's or a docs or changelog site's id under `/metrics`. ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/status/pages/string/hourly' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-docs-metrics.md # Docs analytics across sites `GET https://evohub.io/api/v1/evotrail/metrics/docs` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getDocsMetrics`. Read through EvoTrail: Docs' analytics across every site the key can see. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `docs:site:read`. **AI counts.** Totals, series points, sites and the comparison carry `ai_requests` (every AI request) and `ai_reads` (each page counted once per AI reader per UTC day: the named agent, else the kind of request; MCP tool calls each count). `ai_agents` rows are `{agent, count, reads}` — `count` is requests. `ai_reads_raw_days` is how many days of the range predate the de-duplication; on those days reads equal requests. Older answers have no `ai_reads`: use `ai_requests`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/docs' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-docs-site-metrics.md # One docs site's analytics `GET https://evohub.io/api/v1/evotrail/metrics/docs/sites/{id}` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getDocsSiteMetrics`. Read through EvoTrail: one Docs site's analytics; `id` is the site's id. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `docs:site:read`. **AI counts.** `totals.ai_requests` is every AI request; `totals.ai_reads` counts each page once per AI reader per UTC day (the named agent, else the kind of request; MCP tool calls each count). `ai_reads_raw_days` is how many days of the range predate the de-duplication; on those days reads equal requests. Older answers have no `ai_reads`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, max length 200): The id named by the path: an audit event's id (a UUID) under `/audit`, a status page's or a docs or changelog site's id under `/metrics`. ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/docs/sites/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-docs-site-search-metrics.md # One docs site's search analytics `GET https://evohub.io/api/v1/evotrail/metrics/docs/sites/{id}/search` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getDocsSiteSearchMetrics`. Read through EvoTrail: one Docs site's search analytics, as the Docs console shows them. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `docs:site:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, max length 200): The id named by the path: an audit event's id (a UUID) under `/audit`, a status page's or a docs or changelog site's id under `/metrics`. ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/docs/sites/string/search' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-docs-site-ai-metrics.md # One docs site's AI analytics `GET https://evohub.io/api/v1/evotrail/metrics/docs/sites/{id}/ai` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getDocsSiteAiMetrics`. Read through EvoTrail: one Docs site's AI analytics, as the Docs console shows them. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `docs:site:read`. **Requests and reads.** `totals {requests, reads}`, `series[{day, requests, reads}]`, `kinds[{kind, count, reads}]` and `agents[{agent, count, reads}]`: `requests`/`count` is every AI request, `reads` each page once per AI reader per UTC day (the named agent, else the kind of request; MCP tool calls each count). `reads_raw_days` is how many days of the range predate the de-duplication; on those days reads equal requests. Older answers have no `reads`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, max length 200): The id named by the path: an audit event's id (a UUID) under `/audit`, a status page's or a docs or changelog site's id under `/metrics`. ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/docs/sites/string/ai' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-docs-site-gap-metrics.md # One docs site's gap analytics `GET https://evohub.io/api/v1/evotrail/metrics/docs/sites/{id}/gaps` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getDocsSiteGapMetrics`. Read through EvoTrail: one Docs site's content-gap analytics, as the Docs console shows them. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `docs:site:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, max length 200): The id named by the path: an audit event's id (a UUID) under `/audit`, a status page's or a docs or changelog site's id under `/metrics`. ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/docs/sites/string/gaps' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-changelog-metrics.md # Changelog analytics across sites `GET https://evohub.io/api/v1/evotrail/metrics/changelog` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getChangelogMetrics`. Read through EvoTrail: Changelog's analytics across every site the key can see. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `changelog:site:read`. **AI counts.** Totals, series points, sites and the comparison carry `ai_requests` (every AI request) and `ai_reads` (each page counted once per AI reader per UTC day: the named agent, else the kind of request; MCP tool calls each count). `ai_agents` rows are `{agent, count, reads}` — `count` is requests. `ai_reads_raw_days` is how many days of the range predate the de-duplication; on those days reads equal requests. Older answers have no `ai_reads`: use `ai_requests`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/changelog' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-changelog-site-metrics.md # One changelog's analytics `GET https://evohub.io/api/v1/evotrail/metrics/changelog/sites/{id}` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getChangelogSiteMetrics`. Read through EvoTrail: one changelog's analytics; `id` is the site's id. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `changelog:site:read`. **AI counts.** `totals.ai_requests` is every AI request; `totals.ai_reads` counts each entry (or the list, `llms.txt`, `llms-full.txt`) once per AI reader per UTC day (the named agent, else the kind of request; MCP tool calls each count). `ai` rows are `{kind, count, reads}` and `ai_agents` rows `{agent, count, reads}`, `count` being requests. `ai_reads_raw_days` is how many days of the range predate the de-duplication; on those days reads equal requests. Older answers have no `ai_reads`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, max length 200): The id named by the path: an audit event's id (a UUID) under `/audit`, a status page's or a docs or changelog site's id under `/metrics`. ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/changelog/sites/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-board-metrics.md # Board analytics `GET https://evohub.io/api/v1/evotrail/metrics/board` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getBoardMetrics`. Read through EvoTrail: Board's analytics, as the Board console shows them. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `board:board:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/board' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-retro-metrics.md # Retro analytics `GET https://evohub.io/api/v1/evotrail/metrics/retro` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getRetroMetrics`. Read through EvoTrail: Retro's analytics, as the Retro console shows them. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `retro:board:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/retro' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-retro-stats.md # Retro statistics `GET https://evohub.io/api/v1/evotrail/metrics/retro/stats` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getRetroStats`. Read through EvoTrail: Retro's statistics, as the Retro console shows them. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Not available yet.** EvoTrail does not read this from the product yet, so today this route answers **404 `PRODUCT_NOT_CONNECTED`** for every organization. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `retro:board:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/retro/stats' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-billing-metrics.md # Billing summary `GET https://evohub.io/api/v1/evotrail/metrics/billing` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getBillingMetrics`. Read through EvoTrail: the organization's billing summary. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Not available yet.** EvoTrail does not read this from the product yet, so today this route answers **404 `PRODUCT_NOT_CONNECTED`** for every organization. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `billing:usage:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/billing' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-billing-usage-metrics.md # Billing usage `GET https://evohub.io/api/v1/evotrail/metrics/billing/usage` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getBillingUsageMetrics`. Read through EvoTrail: the organization's metered usage, as the Billing console shows it. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Not available yet.** EvoTrail does not read this from the product yet, so today this route answers **404 `PRODUCT_NOT_CONNECTED`** for every organization. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `billing:usage:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/billing/usage' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-billing-credit-metrics.md # Billing credit `GET https://evohub.io/api/v1/evotrail/metrics/billing/credit` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getBillingCreditMetrics`. Read through EvoTrail: the organization's credit, as the Billing console shows it. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Not available yet.** EvoTrail does not read this from the product yet, so today this route answers **404 `PRODUCT_NOT_CONNECTED`** for every organization. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `billing:usage:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/billing/credit' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-billing-statement-metrics.md # Billing statements `GET https://evohub.io/api/v1/evotrail/metrics/billing/statements` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getBillingStatementMetrics`. Read through EvoTrail: the organization's statements, as the Billing console shows them. EvoTrail passes the query parameters on, asks the product for the key's own view (its teams and scopes), and returns the product's answer byte for byte, including its errors and status codes. A 401 from the product is turned into 403. **Not available yet.** EvoTrail does not read this from the product yet, so today this route answers **404 `PRODUCT_NOT_CONNECTED`** for every organization. **Permission (API-key scope):** `evotrail:metrics:read`. The key also needs `billing:invoice:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The product's answer. Content type: `application/json` Type: `object` Whatever the product answers; see its own reference. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks `evotrail:metrics:read` or the product's permission (`FORBIDDEN`), or the product refused it. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 404 — EvoTrail does not read this product's metrics in this environment (`PRODUCT_NOT_CONNECTED`), or the id is not valid (`NOT_FOUND`). The product's own 404 is passed through as well. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 502 — The product did not answer (`UPSTREAM_UNAVAILABLE`, 502). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 504 — The product took longer than 10 seconds (`UPSTREAM_TIMEOUT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/metrics/billing/statements' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/evotrail/get-evotrail-settings.md # Get the retention periods `GET https://evohub.io/api/v1/evotrail/settings` Part of the [EvoTrail API](https://docs.evohub.io/evotrail.md) reference · operationId `getEvotrailSettings`. EvoTrail's retention, the same for every organization. **Permission (API-key scope):** `evotrail:metrics:read`. ## Authorization Any one of: - `bearerAuth` (evotrail:metrics:read) - `apiKeyHeader` (evotrail:metrics:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The retention periods. Content type: `application/json` Type: `object` - `data` (object, required) - `retention` (object) - `audit_retention_days` (integer) - `activity_retention_days` (integer) - `ip_retention_days` (integer) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 403 — The key lacks the EvoTrail scope, or may read none of the products asked for (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. - `success` (boolean, value `false`) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/evotrail/settings' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall.md # On-Call API Version 1.0 · OpenAPI 3.1.0 The On-Call API: alerts, incidents, schedules, escalation policies, integrations, maintenance windows and the settings around them. It is the API the EvoHub console itself uses. **Authentication.** Send an API key as `Authorization: Bearer evohub_…` or `X-API-Key: evohub_…`. A key works in one organization. See [API keys and scopes](/api-keys-and-scopes) and [Errors](/errors). **Permissions.** Each operation names the permission it needs (`x-evohub-permission`); an API key needs it among its scopes. A key is always held to its scopes, even one made by an administrator. A role that still holds the older `oncall:read` or `oncall:write` permission passes the matching `oncall::read` or `:write` checks (not `oncall:alert:respond`). A refusal is **403 `FORBIDDEN`**, never 401. **Teams.** Schedules, escalation policies and integrations can belong to a team. Those are visible only to callers in that team; others get 404, as if they did not exist. An organization key scoped to a team sees that team's items and the organization-wide ones; an organization key without a team sees the organization-wide ones. A personal key acts as its owner and sees what they see: the organization-wide items and those of the owner's teams. **Responses.** Success is `{"data": …, "success": true}`; paged alert lists add `meta.total`. Errors are `{"error": {"code", "message"}}`, plus `details` for `VALIDATION_FAILED`. Times are RFC 3339 in UTC. Ids are opaque strings. **Not here.** Sending alerts into EvoHub from a monitoring tool uses an integration key, not an API key: see the [Alert ingest API](/alert-ingest) reference. ## Servers - `https://evohub.io` ## Authentication - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Alerts The paged objects: list, raise and answer alerts. - [GET /api/v1/alerts](https://docs.evohub.io/oncall/list-alerts.md): List alerts - [POST /api/v1/alerts](https://docs.evohub.io/oncall/create-alert.md): Raise an alert - [GET /api/v1/alerts/{id}](https://docs.evohub.io/oncall/get-alert.md): Get an alert - [GET /api/v1/alerts/{id}/events](https://docs.evohub.io/oncall/list-alert-events.md): List an alert's timeline - [POST /api/v1/alerts/{id}/events](https://docs.evohub.io/oncall/add-alert-note.md): Add a note to an alert - [POST /api/v1/alerts/{id}/acknowledge](https://docs.evohub.io/oncall/acknowledge-alert.md): Acknowledge an alert - [POST /api/v1/alerts/{id}/resolve](https://docs.evohub.io/oncall/resolve-alert.md): Resolve an alert - [POST /api/v1/alerts/{id}/suppress](https://docs.evohub.io/oncall/suppress-alert.md): Suppress an alert - [POST /api/v1/alerts/{id}/assign](https://docs.evohub.io/oncall/assign-alert.md): Assign an alert - [POST /api/v1/alerts/{id}/takeover](https://docs.evohub.io/oncall/take-over-alert.md): Take over an alert - [POST /api/v1/alerts/{id}/redirect](https://docs.evohub.io/oncall/redirect-alert.md): Redirect an alert to another policy - [POST /api/v1/alerts/acknowledge-bulk](https://docs.evohub.io/oncall/acknowledge-alerts-bulk.md): Acknowledge several alerts - [POST /api/v1/alerts/resolve-bulk](https://docs.evohub.io/oncall/resolve-alerts-bulk.md): Resolve several alerts - [POST /api/v1/alerts/takeover-bulk](https://docs.evohub.io/oncall/take-over-alerts-bulk.md): Take over several alerts - [POST /api/v1/alerts/resolve-by-fingerprint](https://docs.evohub.io/oncall/resolve-alert-by-fingerprint.md): Resolve an alert by fingerprint - [GET /api/v1/alerts/stats](https://docs.evohub.io/oncall/get-alert-stats.md): Count alerts by status - [GET /api/v1/alerts/fields](https://docs.evohub.io/oncall/list-alert-fields.md): List alert field names - [GET /api/v1/alerts/sse](https://docs.evohub.io/oncall/stream-alerts.md): Stream alert changes ## Incidents Human-declared incidents and their timelines. - [GET /api/v1/incidents](https://docs.evohub.io/oncall/list-incidents.md): List incidents - [POST /api/v1/incidents](https://docs.evohub.io/oncall/create-incident.md): Declare an incident - [GET /api/v1/incidents/{id}](https://docs.evohub.io/oncall/get-incident.md): Get an incident - [PUT /api/v1/incidents/{id}](https://docs.evohub.io/oncall/update-incident.md): Update an incident - [POST /api/v1/incidents/{id}/acknowledge](https://docs.evohub.io/oncall/acknowledge-incident.md): Mark an incident identified - [POST /api/v1/incidents/{id}/resolve](https://docs.evohub.io/oncall/resolve-incident.md): Resolve an incident - [GET /api/v1/incidents/{id}/timeline](https://docs.evohub.io/oncall/list-incident-timeline.md): List an incident's timeline - [POST /api/v1/incidents/{id}/timeline](https://docs.evohub.io/oncall/add-incident-timeline-entry.md): Add a timeline entry - [DELETE /api/v1/incidents/{id}/timeline/{entryID}](https://docs.evohub.io/oncall/delete-incident-timeline-entry.md): Delete a timeline entry - [PATCH /api/v1/incidents/{id}/timeline/{entryID}](https://docs.evohub.io/oncall/edit-incident-timeline-entry.md): Edit a timeline entry - [GET /api/v1/incidents/{id}/sse](https://docs.evohub.io/oncall/stream-incident-timeline.md): Stream an incident's timeline ## Status page publishing Publish an incident to one of your status pages. - [GET /api/v1/incidents/{id}/publish](https://docs.evohub.io/oncall/get-incident-publication.md): Get publishing state - [POST /api/v1/incidents/{id}/publish](https://docs.evohub.io/oncall/publish-incident.md): Publish to a status page - [DELETE /api/v1/incidents/{id}/publish](https://docs.evohub.io/oncall/unpublish-incident.md): Remove from the status page ## Postmortems Post-incident reviews. - [GET /api/v1/incidents/{id}/postmortem](https://docs.evohub.io/oncall/get-postmortem.md): Get an incident's postmortem - [PUT /api/v1/incidents/{id}/postmortem](https://docs.evohub.io/oncall/replace-postmortem.md): Write an incident's postmortem (PUT) - [POST /api/v1/incidents/{id}/postmortem](https://docs.evohub.io/oncall/save-postmortem.md): Write an incident's postmortem ## Schedules On-call schedules and who is on call. - [GET /api/v1/schedules](https://docs.evohub.io/oncall/list-schedules.md): List schedules - [POST /api/v1/schedules](https://docs.evohub.io/oncall/create-schedule.md): Create a schedule - [GET /api/v1/schedules/{id}](https://docs.evohub.io/oncall/get-schedule.md): Get a schedule - [PUT /api/v1/schedules/{id}](https://docs.evohub.io/oncall/update-schedule.md): Update a schedule - [DELETE /api/v1/schedules/{id}](https://docs.evohub.io/oncall/delete-schedule.md): Delete a schedule - [GET /api/v1/schedules/{id}/on-call-now](https://docs.evohub.io/oncall/get-schedule-on-call-now.md): Who is on call on a schedule - [GET /api/v1/on-call-now](https://docs.evohub.io/oncall/list-on-call-now.md): Who is on call everywhere - [GET /api/v1/schedules/{id}/shifts](https://docs.evohub.io/oncall/list-schedule-shifts.md): List a schedule's shifts over a range - [GET /api/v1/schedules/my-on-call](https://docs.evohub.io/oncall/get-my-on-call.md): My on-call shifts ## Schedule layers Rotation layers and the people in them. - [POST /api/v1/schedules/{id}/layers](https://docs.evohub.io/oncall/create-schedule-layer.md): Add a layer - [POST /api/v1/schedules/{id}/layers/preview](https://docs.evohub.io/oncall/preview-schedule-layer.md): Preview a layer before adding it - [DELETE /api/v1/schedules/{id}/layers/{layerID}](https://docs.evohub.io/oncall/delete-schedule-layer.md): Delete a layer - [PATCH /api/v1/schedules/{id}/layers/{layerID}](https://docs.evohub.io/oncall/update-schedule-layer.md): Update a layer - [POST /api/v1/schedules/{id}/layers/{layerID}/revise](https://docs.evohub.io/oncall/revise-schedule-layer.md): Change a layer from a later moment - [POST /api/v1/schedules/{id}/layers/{layerID}/revise/preview](https://docs.evohub.io/oncall/preview-schedule-layer-revision.md): Preview a change to a layer - [PUT /api/v1/schedules/{id}/layers/{layerID}/rotations](https://docs.evohub.io/oncall/reorder-schedule-rotations.md): Reorder a layer's rota - [POST /api/v1/schedules/{id}/layers/{layerID}/rotations](https://docs.evohub.io/oncall/add-schedule-rotation.md): Add a person to a layer - [DELETE /api/v1/schedules/{id}/layers/{layerID}/rotations/{rotationID}](https://docs.evohub.io/oncall/delete-schedule-rotation.md): Remove a person from a layer ## Overrides Temporary cover on a schedule. - [GET /api/v1/schedules/{id}/overrides](https://docs.evohub.io/oncall/list-schedule-overrides.md): List overrides - [POST /api/v1/schedules/{id}/overrides](https://docs.evohub.io/oncall/create-schedule-override.md): Create an override - [DELETE /api/v1/schedules/{id}/overrides/{overrideID}](https://docs.evohub.io/oncall/delete-schedule-override.md): Delete an override ## Escalation policies Who is notified about an alert, in what order. - [GET /api/v1/escalation-policies](https://docs.evohub.io/oncall/list-escalation-policies.md): List escalation policies - [POST /api/v1/escalation-policies](https://docs.evohub.io/oncall/create-escalation-policy.md): Create an escalation policy - [GET /api/v1/escalation-policies/{id}](https://docs.evohub.io/oncall/get-escalation-policy.md): Get an escalation policy - [PUT /api/v1/escalation-policies/{id}](https://docs.evohub.io/oncall/update-escalation-policy.md): Update an escalation policy - [DELETE /api/v1/escalation-policies/{id}](https://docs.evohub.io/oncall/delete-escalation-policy.md): Delete an escalation policy ## Integrations Monitoring tools that send alerts, and their keys. - [GET /api/v1/integrations](https://docs.evohub.io/oncall/list-integrations.md): List integrations - [POST /api/v1/integrations](https://docs.evohub.io/oncall/create-integration.md): Create an integration - [GET /api/v1/integrations/{id}](https://docs.evohub.io/oncall/get-integration.md): Get an integration - [PUT /api/v1/integrations/{id}](https://docs.evohub.io/oncall/update-integration.md): Update an integration - [DELETE /api/v1/integrations/{id}](https://docs.evohub.io/oncall/delete-integration.md): Delete an integration - [PUT /api/v1/integrations/{id}/signing-secret](https://docs.evohub.io/oncall/set-integration-signing-secret.md): Set or generate a signing secret - [DELETE /api/v1/integrations/{id}/signing-secret](https://docs.evohub.io/oncall/clear-integration-signing-secret.md): Remove a signing secret ## Maintenance windows Planned work that holds back paging. - [GET /api/v1/maintenance-windows](https://docs.evohub.io/oncall/list-maintenance-windows.md): List maintenance windows - [POST /api/v1/maintenance-windows](https://docs.evohub.io/oncall/create-maintenance-window.md): Schedule a maintenance window - [GET /api/v1/maintenance-windows/{id}](https://docs.evohub.io/oncall/get-maintenance-window.md): Get a maintenance window - [DELETE /api/v1/maintenance-windows/{id}](https://docs.evohub.io/oncall/delete-maintenance-window.md): Delete a maintenance window - [PATCH /api/v1/maintenance-windows/{id}](https://docs.evohub.io/oncall/update-maintenance-window.md): Update a maintenance window - [POST /api/v1/maintenance-windows/{id}/complete](https://docs.evohub.io/oncall/complete-maintenance-window.md): End a maintenance window - [GET /api/v1/maintenance-windows/{id}/updates](https://docs.evohub.io/oncall/list-maintenance-updates.md): List a window's updates - [POST /api/v1/maintenance-windows/{id}/updates](https://docs.evohub.io/oncall/add-maintenance-update.md): Post an update - [DELETE /api/v1/maintenance-windows/{id}/updates/{updateID}](https://docs.evohub.io/oncall/delete-maintenance-update.md): Delete an update - [PATCH /api/v1/maintenance-windows/{id}/updates/{updateID}](https://docs.evohub.io/oncall/edit-maintenance-update.md): Edit an update ## Analytics MTTA, MTTR, noise and load. - [GET /api/v1/alerts/analytics](https://docs.evohub.io/oncall/get-alert-analytics.md): Alert analytics - [GET /api/v1/incidents/analytics](https://docs.evohub.io/oncall/get-incident-analytics.md): Incident analytics ## Message templates Default status-update texts and the voice-call template. - [GET /api/v1/update-templates](https://docs.evohub.io/oncall/list-update-templates.md): List status-update templates - [PUT /api/v1/update-templates](https://docs.evohub.io/oncall/save-update-template.md): Save a status-update template - [GET /api/v1/voice-template](https://docs.evohub.io/oncall/get-voice-template.md): Get the voice-call template - [PUT /api/v1/voice-template](https://docs.evohub.io/oncall/save-voice-template.md): Save the voice-call template - [POST /api/v1/voice-template/preview](https://docs.evohub.io/oncall/preview-voice-template.md): Preview a voice-call template ## My notifications How and when the caller is notified. - [GET /api/v1/notification-preferences](https://docs.evohub.io/oncall/list-notification-preferences.md): List my notification methods - [POST /api/v1/notification-preferences](https://docs.evohub.io/oncall/create-notification-preference.md): Add a notification method - [PUT /api/v1/notification-preferences/{id}](https://docs.evohub.io/oncall/update-notification-preference.md): Update a notification method - [DELETE /api/v1/notification-preferences/{id}](https://docs.evohub.io/oncall/delete-notification-preference.md): Remove a notification method - [PATCH /api/v1/notification-preferences/{id}](https://docs.evohub.io/oncall/update-notification-preference-partial.md): Update a notification method (PATCH) - [GET /api/v1/notification-schedule](https://docs.evohub.io/oncall/get-notification-schedule.md): Get my notification schedule - [PUT /api/v1/notification-schedule](https://docs.evohub.io/oncall/save-notification-schedule.md): Save my notification schedule ## Slack The organization's Slack workspace. - [GET /api/v1/slack](https://docs.evohub.io/oncall/get-slack-connection.md): Get the Slack connection - [DELETE /api/v1/slack](https://docs.evohub.io/oncall/disconnect-slack.md): Disconnect Slack - [POST /api/v1/slack/install](https://docs.evohub.io/oncall/start-slack-install.md): Start connecting Slack - [GET /api/v1/slack/channels](https://docs.evohub.io/oncall/list-slack-channels.md): List Slack channels - [PUT /api/v1/slack/default-channel](https://docs.evohub.io/oncall/set-slack-default-channel.md): Set the default Slack channel ## Microsoft Teams Teams channels and the action links on Teams cards. - [GET /api/v1/chat](https://docs.evohub.io/oncall/get-chat-status.md): Get chat availability - [GET /api/v1/chat/channels](https://docs.evohub.io/oncall/list-chat-channels.md): List Teams channels - [POST /api/v1/chat/channels](https://docs.evohub.io/oncall/create-chat-channel.md): Add a Teams channel - [DELETE /api/v1/chat/channels/{id}](https://docs.evohub.io/oncall/delete-chat-channel.md): Remove a Teams channel - [PATCH /api/v1/chat/channels/{id}](https://docs.evohub.io/oncall/update-chat-channel.md): Update a Teams channel - [POST /api/v1/chat/channels/{id}/test](https://docs.evohub.io/oncall/test-chat-channel.md): Send a test message - [GET /api/v1/chat/actions/{token}](https://docs.evohub.io/oncall/preview-chat-action.md): Preview a Teams action link - [POST /api/v1/chat/actions/{token}](https://docs.evohub.io/oncall/run-chat-action.md): Run a Teams action link ## Import Bring schedules, policies and integrations over from Opsgenie or PagerDuty. - [POST /api/v1/import/opsgenie/preview](https://docs.evohub.io/oncall/preview-opsgenie-import.md): Preview an import from Opsgenie - [POST /api/v1/import/opsgenie/apply](https://docs.evohub.io/oncall/apply-opsgenie-import.md): Import from Opsgenie - [POST /api/v1/import/pagerduty/preview](https://docs.evohub.io/oncall/preview-pager-duty-import.md): Preview an import from PagerDuty - [POST /api/v1/import/pagerduty/apply](https://docs.evohub.io/oncall/apply-pager-duty-import.md): Import from PagerDuty ## Audit log Who changed what in On-Call. - [GET /api/v1/oncall-audit-logs](https://docs.evohub.io/oncall/list-oncall-audit-logs.md): List the On-Call audit log --- Source: https://docs.evohub.io/oncall/list-alerts.md # List alerts `GET https://evohub.io/api/v1/alerts` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listAlerts`. Lists the organization's alerts, newest first unless `sort` says otherwise. The list is paged: `limit` (default 50) and `offset`, with the number of matching alerts in `meta.total`. Every filter is optional and they combine. `team_id` narrows the list, it never hides anything you could otherwise see: a team id returns that team's alerts together with the organization-wide ones (alerts with no team), and `none` returns only the organization-wide ones. **Permission (API-key scope):** `oncall:alert:read`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:read) - `apiKeyHeader` (oncall:alert:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `status` (string, one of `triggered`, `acknowledged`, `resolved`, `suppressed`, `open`, example `open`): `triggered`, `acknowledged`, `resolved`, `suppressed`, or `open` for triggered and acknowledged together. - `severity` (AlertSeverity, one of `critical`, `high`, `medium`, `low`, `info`): One severity. - `source` (string, example `prometheus`): The kind of integration or origin, for example `prometheus` or `manual`. - `q` (string): Free-text search over the title, source and labels (names and values; case-insensitive, partial match). - `assignee` (string, example `me`): A user id, `me` for the caller, or `unassigned`. - `escalation_policy_id` (string): Only alerts routed to this escalation policy. - `integration_id` (string): Only alerts opened by this integration. - `team_id` (string): A team id (that team's alerts plus organization-wide ones) or `none` (organization-wide alerts only). - `created_from` (string, example `2026-10-01`): Earliest creation time, inclusive: RFC 3339, or a date (`YYYY-MM-DD`, start of the UTC day). An unreadable value is ignored. - `created_to` (string, example `2026-10-09`): Latest creation time, inclusive: RFC 3339, or a date (`YYYY-MM-DD`, the whole UTC day). An unreadable value is ignored. - `sort` (string, one of `created_at`, `updated_at`, `severity`, `status`, `source`, `assigned_to`, `title`, default `created_at`): Column to sort by. - `dir` (string, one of `asc`, `desc`, default `desc`): Sort direction. Empty values sort last either way. - `limit` (integer, default `50`, min 0): Page size. 0 or omitted means 50; more than 500 returns 500. Anything but a whole number of 0 or more is refused (400 `VALIDATION_FAILED`, field `limit`). - `offset` (integer, default `0`, min 0): How many items to skip. Anything but a whole number of 0 or more is refused (400 `VALIDATION_FAILED`, field `offset`). ## Responses ### 200 — A page of alerts. Content type: `application/json` Type: `object` - `data` (array of Alert, required) - `id` (string, required) - `org_id` (string, required) - `team_id` (string): Owning team; empty for an organization-wide alert. - `escalation_policy_id` (string): Empty when no policy pages for it. - `incident_id` (string) - `integration_id` (string): The integration that opened it; empty for alerts raised by hand or by another EvoHub product. - `fingerprint` (string): Deduplication key: while an alert with it is open, the same fingerprint retriggers it instead of opening another. - `title` (string, required) - `description` (string) - `severity` (AlertSeverity, required, one of `critical`, `high`, `medium`, `low`, `info`) - `status` (AlertStatus, required, one of `triggered`, `acknowledged`, `resolved`, `suppressed`) - `source` (string, required): Where it came from: an integration kind such as `prometheus`, `api`, `webhook`, or `manual`. - `labels` (object) - Other keys: string - `annotations` (object) - Other keys: string - `current_escalation_step` (integer): The escalation step reached so far. - `acknowledged_by` (string) - `acknowledged_at` (string (date-time)) - `resolved_by` (string) - `resolved_at` (string (date-time)) - `snoozed_until` (string (date-time)) - `snoozed_by` (string) - `assigned_to` (string): User id of the assignee; empty when unassigned. - `notification_count` (integer) - `retrigger_count` (integer): How many times the same fingerprint arrived again while it was open. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) - `meta` (object, required) - `total` (integer): Alerts matching the filters, ignoring the page. ### 400 — `sort` names a column that cannot be sorted on, `dir` is not `asc` or `desc`, or `limit` or `offset` is not a whole number of 0 or more (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/alerts' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/create-alert.md # Raise an alert `POST https://evohub.io/api/v1/alerts` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `createAlert`. Raises an alert by hand and starts its escalation, exactly as an alert from an integration would: people are paged and notifications are billed. `fingerprint` deduplicates: when an open alert in the organization already carries it, no new alert is created — the existing one is counted as a retrigger and returned. `team_id` defaults to the caller's first team. To send alerts from a monitoring tool, use an integration instead (see the Alert ingest API reference). **Permission (API-key scope):** `oncall:alert:write`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:write) - `apiKeyHeader` (oncall:alert:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `AlertCreate` - `title` (string, required) - `severity` (AlertSeverity, required, one of `critical`, `high`, `medium`, `low`, `info`) - `description` (string) - `source` (string, default `manual`): Defaults to `manual`. - `fingerprint` (string): Deduplication key; a random one when omitted. - `escalation_policy_id` (string): The policy that pages for it. - `team_id` (string): Defaults to the caller's first team. - `labels` (object) - Other keys: string - `annotations` (object) - Other keys: string ## Responses ### 201 — The alert that was created, or the open alert with the same fingerprint. Content type: `application/json` Type: `object` - `data` (Alert, required) - `id` (string, required) - `org_id` (string, required) - `team_id` (string): Owning team; empty for an organization-wide alert. - `escalation_policy_id` (string): Empty when no policy pages for it. - `incident_id` (string) - `integration_id` (string): The integration that opened it; empty for alerts raised by hand or by another EvoHub product. - `fingerprint` (string): Deduplication key: while an alert with it is open, the same fingerprint retriggers it instead of opening another. - `title` (string, required) - `description` (string) - `severity` (AlertSeverity, required, one of `critical`, `high`, `medium`, `low`, `info`) - `status` (AlertStatus, required, one of `triggered`, `acknowledged`, `resolved`, `suppressed`) - `source` (string, required): Where it came from: an integration kind such as `prometheus`, `api`, `webhook`, or `manual`. - `labels` (object) - Other keys: string - `annotations` (object) - Other keys: string - `current_escalation_step` (integer): The escalation step reached so far. - `acknowledged_by` (string) - `acknowledged_at` (string (date-time)) - `resolved_by` (string) - `resolved_at` (string (date-time)) - `snoozed_until` (string (date-time)) - `snoozed_by` (string) - `assigned_to` (string): User id of the assignee; empty when unassigned. - `notification_count` (integer) - `retrigger_count` (integer): How many times the same fingerprint arrived again while it was open. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `title` or `severity` is missing, `severity` is not one of critical, high, medium, low or info, or `escalation_policy_id` names no policy of the organization (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "title": "Checkout error budget exhausted", "labels": { "service": "checkout" }, "severity": "high", "description": "Raised from the deploy pipeline", "fingerprint": "checkout-error-budget", "escalation_policy_id": "3f8a0b2c-4d6e-4f8a-b1c3-d5e7f9a1b3c5" }' ``` --- Source: https://docs.evohub.io/oncall/get-alert.md # Get an alert `GET https://evohub.io/api/v1/alerts/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getAlert`. Returns one alert. **Permission (API-key scope):** `oncall:alert:read`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:read) - `apiKeyHeader` (oncall:alert:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f`): The alert's id. ## Responses ### 200 — The alert. Content type: `application/json` Type: `object` - `data` (Alert, required) - `id` (string, required) - `org_id` (string, required) - `team_id` (string): Owning team; empty for an organization-wide alert. - `escalation_policy_id` (string): Empty when no policy pages for it. - `incident_id` (string) - `integration_id` (string): The integration that opened it; empty for alerts raised by hand or by another EvoHub product. - `fingerprint` (string): Deduplication key: while an alert with it is open, the same fingerprint retriggers it instead of opening another. - `title` (string, required) - `description` (string) - `severity` (AlertSeverity, required, one of `critical`, `high`, `medium`, `low`, `info`) - `status` (AlertStatus, required, one of `triggered`, `acknowledged`, `resolved`, `suppressed`) - `source` (string, required): Where it came from: an integration kind such as `prometheus`, `api`, `webhook`, or `manual`. - `labels` (object) - Other keys: string - `annotations` (object) - Other keys: string - `current_escalation_step` (integer): The escalation step reached so far. - `acknowledged_by` (string) - `acknowledged_at` (string (date-time)) - `resolved_by` (string) - `resolved_at` (string (date-time)) - `snoozed_until` (string (date-time)) - `snoozed_by` (string) - `assigned_to` (string): User id of the assignee; empty when unassigned. - `notification_count` (integer) - `retrigger_count` (integer): How many times the same fingerprint arrived again while it was open. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No alert with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/alerts/9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-alert-events.md # List an alert's timeline `GET https://evohub.io/api/v1/alerts/{id}/events` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listAlertEvents`. Returns everything that happened to the alert, oldest first: creation, notifications sent, acknowledgements, notes, escalation steps and so on. **Permission (API-key scope):** `oncall:alert:read`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:read) - `apiKeyHeader` (oncall:alert:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f`): The alert's id. ## Responses ### 200 — The alert's events. Content type: `application/json` Type: `object` - `data` (array of AlertEvent, required) - `id` (string) - `alert_id` (string) - `event_type` (string, one of `created`, `acknowledged`, `resolved`, `reopened`, `note_added`, `snoozed`, `assigned`, `taken_over`, `suppressed`, `voice_call_sent`, `sms_sent`, `email_sent`, `push_sent`, `call_not_answered`, `manual_escalation`, `retriggered`, `redirected`, `not_routed`, `notification_blocked`, `notification_suppressed`, `ack_timed_out`) - `actor_id` (string): Who did it; empty for EvoHub itself. - `note` (string) - `metadata` (object | null): Details that depend on the event type. - Other keys: any - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No alert with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/alerts/9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f/events' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/add-alert-note.md # Add a note to an alert `POST https://evohub.io/api/v1/alerts/{id}/events` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `addAlertNote`. Adds a note to the alert's timeline. It does not change the alert's status. **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f`): The alert's id. ## Request body (required) Content type: `application/json` Type: `object` - `message` (string, required): The note. ## Responses ### 201 — The note as stored. Content type: `application/json` Type: `object` - `data` (AlertEvent, required) - `id` (string) - `alert_id` (string) - `event_type` (string, one of `created`, `acknowledged`, `resolved`, `reopened`, `note_added`, `snoozed`, `assigned`, `taken_over`, `suppressed`, `voice_call_sent`, `sms_sent`, `email_sent`, `push_sent`, `call_not_answered`, `manual_escalation`, `retriggered`, `redirected`, `not_routed`, `notification_blocked`, `notification_suppressed`, `ack_timed_out`) - `actor_id` (string): Who did it; empty for EvoHub itself. - `note` (string) - `metadata` (object | null): Details that depend on the event type. - Other keys: any - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`) or `message` is empty (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No alert with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts/9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f/events' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "message": "Restarting the payments pods." }' ``` --- Source: https://docs.evohub.io/oncall/acknowledge-alert.md # Acknowledge an alert `POST https://evohub.io/api/v1/alerts/{id}/acknowledge` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `acknowledgeAlert`. Marks the alert acknowledged by the caller. When the alert's escalation policy has an ack timeout the escalation pauses and resumes if the alert is still unresolved when it runs out; otherwise the escalation stops. Acknowledging an alert that is already acknowledged succeeds and changes nothing. A resolved alert cannot be acknowledged. No request body. **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f`): The alert's id. ## Responses ### 200 — The alert after the change. Content type: `application/json` Type: `object` - `data` (Alert, required) - `id` (string, required) - `org_id` (string, required) - `team_id` (string): Owning team; empty for an organization-wide alert. - `escalation_policy_id` (string): Empty when no policy pages for it. - `incident_id` (string) - `integration_id` (string): The integration that opened it; empty for alerts raised by hand or by another EvoHub product. - `fingerprint` (string): Deduplication key: while an alert with it is open, the same fingerprint retriggers it instead of opening another. - `title` (string, required) - `description` (string) - `severity` (AlertSeverity, required, one of `critical`, `high`, `medium`, `low`, `info`) - `status` (AlertStatus, required, one of `triggered`, `acknowledged`, `resolved`, `suppressed`) - `source` (string, required): Where it came from: an integration kind such as `prometheus`, `api`, `webhook`, or `manual`. - `labels` (object) - Other keys: string - `annotations` (object) - Other keys: string - `current_escalation_step` (integer): The escalation step reached so far. - `acknowledged_by` (string) - `acknowledged_at` (string (date-time)) - `resolved_by` (string) - `resolved_at` (string (date-time)) - `snoozed_until` (string (date-time)) - `snoozed_by` (string) - `assigned_to` (string): User id of the assignee; empty when unassigned. - `notification_count` (integer) - `retrigger_count` (integer): How many times the same fingerprint arrived again while it was open. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No alert with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — The alert is already resolved (`ALERT_RESOLVED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts/9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f/acknowledge' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/resolve-alert.md # Resolve an alert `POST https://evohub.io/api/v1/alerts/{id}/resolve` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `resolveAlert`. Resolves the alert and stops its escalation. Resolving an alert that is already resolved succeeds and keeps who resolved it first. No request body. **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f`): The alert's id. ## Responses ### 200 — The alert after the change. Content type: `application/json` Type: `object` - `data` (Alert, required) - `id` (string, required) - `org_id` (string, required) - `team_id` (string): Owning team; empty for an organization-wide alert. - `escalation_policy_id` (string): Empty when no policy pages for it. - `incident_id` (string) - `integration_id` (string): The integration that opened it; empty for alerts raised by hand or by another EvoHub product. - `fingerprint` (string): Deduplication key: while an alert with it is open, the same fingerprint retriggers it instead of opening another. - `title` (string, required) - `description` (string) - `severity` (AlertSeverity, required, one of `critical`, `high`, `medium`, `low`, `info`) - `status` (AlertStatus, required, one of `triggered`, `acknowledged`, `resolved`, `suppressed`) - `source` (string, required): Where it came from: an integration kind such as `prometheus`, `api`, `webhook`, or `manual`. - `labels` (object) - Other keys: string - `annotations` (object) - Other keys: string - `current_escalation_step` (integer): The escalation step reached so far. - `acknowledged_by` (string) - `acknowledged_at` (string (date-time)) - `resolved_by` (string) - `resolved_at` (string (date-time)) - `snoozed_until` (string (date-time)) - `snoozed_by` (string) - `assigned_to` (string): User id of the assignee; empty when unassigned. - `notification_count` (integer) - `retrigger_count` (integer): How many times the same fingerprint arrived again while it was open. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No alert with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts/9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f/resolve' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/suppress-alert.md # Suppress an alert `POST https://evohub.io/api/v1/alerts/{id}/suppress` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `suppressAlert`. Marks an unresolved alert suppressed and stops its escalation. An alert that is already suppressed or resolved is returned unchanged. No request body. **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f`): The alert's id. ## Responses ### 200 — The alert. Content type: `application/json` Type: `object` - `data` (Alert, required) - `id` (string, required) - `org_id` (string, required) - `team_id` (string): Owning team; empty for an organization-wide alert. - `escalation_policy_id` (string): Empty when no policy pages for it. - `incident_id` (string) - `integration_id` (string): The integration that opened it; empty for alerts raised by hand or by another EvoHub product. - `fingerprint` (string): Deduplication key: while an alert with it is open, the same fingerprint retriggers it instead of opening another. - `title` (string, required) - `description` (string) - `severity` (AlertSeverity, required, one of `critical`, `high`, `medium`, `low`, `info`) - `status` (AlertStatus, required, one of `triggered`, `acknowledged`, `resolved`, `suppressed`) - `source` (string, required): Where it came from: an integration kind such as `prometheus`, `api`, `webhook`, or `manual`. - `labels` (object) - Other keys: string - `annotations` (object) - Other keys: string - `current_escalation_step` (integer): The escalation step reached so far. - `acknowledged_by` (string) - `acknowledged_at` (string (date-time)) - `resolved_by` (string) - `resolved_at` (string (date-time)) - `snoozed_until` (string (date-time)) - `snoozed_by` (string) - `assigned_to` (string): User id of the assignee; empty when unassigned. - `notification_count` (integer) - `retrigger_count` (integer): How many times the same fingerprint arrived again while it was open. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No alert with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts/9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f/suppress' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/assign-alert.md # Assign an alert `POST https://evohub.io/api/v1/alerts/{id}/assign` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `assignAlert`. Assigns the alert to a person. It changes the assignee only, not the status. The person must be a member of the organization who can respond to alerts (holds `oncall:alert:respond`). **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f`): The alert's id. ## Request body (required) Content type: `application/json` Type: `object` - `assignee_id` (string, required): The user id of the new assignee. ## Responses ### 200 — The alert after the change. Content type: `application/json` Type: `object` - `data` (Alert, required) - `id` (string, required) - `org_id` (string, required) - `team_id` (string): Owning team; empty for an organization-wide alert. - `escalation_policy_id` (string): Empty when no policy pages for it. - `incident_id` (string) - `integration_id` (string): The integration that opened it; empty for alerts raised by hand or by another EvoHub product. - `fingerprint` (string): Deduplication key: while an alert with it is open, the same fingerprint retriggers it instead of opening another. - `title` (string, required) - `description` (string) - `severity` (AlertSeverity, required, one of `critical`, `high`, `medium`, `low`, `info`) - `status` (AlertStatus, required, one of `triggered`, `acknowledged`, `resolved`, `suppressed`) - `source` (string, required): Where it came from: an integration kind such as `prometheus`, `api`, `webhook`, or `manual`. - `labels` (object) - Other keys: string - `annotations` (object) - Other keys: string - `current_escalation_step` (integer): The escalation step reached so far. - `acknowledged_by` (string) - `acknowledged_at` (string (date-time)) - `resolved_by` (string) - `resolved_at` (string (date-time)) - `snoozed_until` (string (date-time)) - `snoozed_by` (string) - `assigned_to` (string): User id of the assignee; empty when unassigned. - `notification_count` (integer) - `retrigger_count` (integer): How many times the same fingerprint arrived again while it was open. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`) or `assignee_id` is empty (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such alert (`NOT_FOUND`), or the person is not a member of the organization (`NOT_A_MEMBER`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 422 — The person cannot respond to alerts: they do not hold `oncall:alert:respond` (`CANNOT_RESPOND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Whether the person may be paged could not be checked right now; nothing was changed (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts/9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f/assign' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "assignee_id": "4c2a9e7b-1d3f-4a8c-b6e5-9f0a1b2c3d4e" }' ``` --- Source: https://docs.evohub.io/oncall/take-over-alert.md # Take over an alert `POST https://evohub.io/api/v1/alerts/{id}/takeover` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `takeOverAlert`. "I've got this": assigns the alert to the caller and, when it is still triggered, acknowledges it. The escalation stops. The previous assignee is recorded on the alert's timeline. With an organization API key the key itself becomes the assignee. No request body. **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f`): The alert's id. ## Responses ### 200 — The alert after the change. Content type: `application/json` Type: `object` - `data` (Alert, required) - `id` (string, required) - `org_id` (string, required) - `team_id` (string): Owning team; empty for an organization-wide alert. - `escalation_policy_id` (string): Empty when no policy pages for it. - `incident_id` (string) - `integration_id` (string): The integration that opened it; empty for alerts raised by hand or by another EvoHub product. - `fingerprint` (string): Deduplication key: while an alert with it is open, the same fingerprint retriggers it instead of opening another. - `title` (string, required) - `description` (string) - `severity` (AlertSeverity, required, one of `critical`, `high`, `medium`, `low`, `info`) - `status` (AlertStatus, required, one of `triggered`, `acknowledged`, `resolved`, `suppressed`) - `source` (string, required): Where it came from: an integration kind such as `prometheus`, `api`, `webhook`, or `manual`. - `labels` (object) - Other keys: string - `annotations` (object) - Other keys: string - `current_escalation_step` (integer): The escalation step reached so far. - `acknowledged_by` (string) - `acknowledged_at` (string (date-time)) - `resolved_by` (string) - `resolved_at` (string (date-time)) - `snoozed_until` (string (date-time)) - `snoozed_by` (string) - `assigned_to` (string): User id of the assignee; empty when unassigned. - `notification_count` (integer) - `retrigger_count` (integer): How many times the same fingerprint arrived again while it was open. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No alert with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — A resolved alert cannot be taken over (`ALERT_RESOLVED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts/9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f/takeover' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/redirect-alert.md # Redirect an alert to another policy `POST https://evohub.io/api/v1/alerts/{id}/redirect` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `redirectAlert`. Points an unresolved alert at a different escalation policy and restarts the escalation from that policy's first step. An acknowledged alert becomes triggered and unowned again, so the new policy actually pages. **Permission (API-key scope):** `oncall:alert:write`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:write) - `apiKeyHeader` (oncall:alert:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f`): The alert's id. ## Request body (required) Content type: `application/json` Type: `object` - `escalation_policy_id` (string, required) ## Responses ### 200 — The alert after the change. Content type: `application/json` Type: `object` - `data` (Alert, required) - `id` (string, required) - `org_id` (string, required) - `team_id` (string): Owning team; empty for an organization-wide alert. - `escalation_policy_id` (string): Empty when no policy pages for it. - `incident_id` (string) - `integration_id` (string): The integration that opened it; empty for alerts raised by hand or by another EvoHub product. - `fingerprint` (string): Deduplication key: while an alert with it is open, the same fingerprint retriggers it instead of opening another. - `title` (string, required) - `description` (string) - `severity` (AlertSeverity, required, one of `critical`, `high`, `medium`, `low`, `info`) - `status` (AlertStatus, required, one of `triggered`, `acknowledged`, `resolved`, `suppressed`) - `source` (string, required): Where it came from: an integration kind such as `prometheus`, `api`, `webhook`, or `manual`. - `labels` (object) - Other keys: string - `annotations` (object) - Other keys: string - `current_escalation_step` (integer): The escalation step reached so far. - `acknowledged_by` (string) - `acknowledged_at` (string (date-time)) - `resolved_by` (string) - `resolved_at` (string (date-time)) - `snoozed_until` (string (date-time)) - `snoozed_by` (string) - `assigned_to` (string): User id of the assignee; empty when unassigned. - `notification_count` (integer) - `retrigger_count` (integer): How many times the same fingerprint arrived again while it was open. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), `escalation_policy_id` is empty (`VALIDATION_FAILED`), or it names no policy of the organization (`ESCALATION_POLICY_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No alert with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — The alert is already resolved (`ALERT_RESOLVED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts/9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f/redirect' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "escalation_policy_id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d" }' ``` --- Source: https://docs.evohub.io/oncall/acknowledge-alerts-bulk.md # Acknowledge several alerts `POST https://evohub.io/api/v1/alerts/acknowledge-bulk` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `acknowledgeAlertsBulk`. Each alert in `ids` is acknowledged on its own, exactly as the single-alert endpoint would. The answer lists which ids succeeded and which failed (for example not found, or already resolved); one failure does not stop the rest. A list with no ids in it comes back as `[]`. **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `ids` (array of string, required, min items 1): Alert ids. ## Responses ### 200 — Which ids succeeded and which failed. Content type: `application/json` Type: `object` - `data` (BulkResult, required) - `succeeded` (array of string) - `failed` (array of string) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`) or `ids` is empty (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts/acknowledge-bulk' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "ids": [ "9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f" ] }' ``` --- Source: https://docs.evohub.io/oncall/resolve-alerts-bulk.md # Resolve several alerts `POST https://evohub.io/api/v1/alerts/resolve-bulk` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `resolveAlertsBulk`. Each alert in `ids` is resolved on its own, exactly as the single-alert endpoint would. The answer lists which ids succeeded and which failed (for example not found; an alert that is already resolved counts as succeeded); one failure does not stop the rest. A list with no ids in it comes back as `[]`. **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `ids` (array of string, required, min items 1): Alert ids. ## Responses ### 200 — Which ids succeeded and which failed. Content type: `application/json` Type: `object` - `data` (BulkResult, required) - `succeeded` (array of string) - `failed` (array of string) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`) or `ids` is empty (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts/resolve-bulk' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "ids": [ "9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f" ] }' ``` --- Source: https://docs.evohub.io/oncall/take-over-alerts-bulk.md # Take over several alerts `POST https://evohub.io/api/v1/alerts/takeover-bulk` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `takeOverAlertsBulk`. Each alert in `ids` is taken over (assigned to the caller and acknowledged when still triggered) on its own, exactly as the single-alert endpoint would. The answer lists which ids succeeded and which failed (for example not found, or already resolved); one failure does not stop the rest. A list with no ids in it comes back as `[]`. **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `ids` (array of string, required, min items 1): Alert ids. ## Responses ### 200 — Which ids succeeded and which failed. Content type: `application/json` Type: `object` - `data` (BulkResult, required) - `succeeded` (array of string) - `failed` (array of string) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`) or `ids` is empty (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts/takeover-bulk' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "ids": [ "9d4e2f1a-6b3c-4d8e-a7f9-0c1b2a3d4e5f" ] }' ``` --- Source: https://docs.evohub.io/oncall/resolve-alert-by-fingerprint.md # Resolve an alert by fingerprint `POST https://evohub.io/api/v1/alerts/resolve-by-fingerprint` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `resolveAlertByFingerprint`. Resolves the open alert carrying this fingerprint. When no open alert has it, nothing happens and the answer is still 200. **Permission (API-key scope):** `oncall:alert:write`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:write) - `apiKeyHeader` (oncall:alert:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `fingerprint` (string, required) ## Responses ### 200 — Always `{"resolved": true}` once the request was read. Content type: `application/json` Type: `object` - `data` (object, required) - `resolved` (boolean, value `true`) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`) or `fingerprint` is empty (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/alerts/resolve-by-fingerprint' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "fingerprint": "checkout-5xx-rate" }' ``` --- Source: https://docs.evohub.io/oncall/get-alert-stats.md # Count alerts by status `GET https://evohub.io/api/v1/alerts/stats` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getAlertStats`. How many of the organization's alerts there are in total and per status, over all time. **Permission (API-key scope):** `oncall:alert:read`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:read) - `apiKeyHeader` (oncall:alert:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The counts. Content type: `application/json` Type: `object` - `data` (AlertStats, required) - `total` (integer) - `triggered` (integer) - `acknowledged` (integer) - `resolved` (integer) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/alerts/stats' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-alert-fields.md # List alert field names `GET https://evohub.io/api/v1/alerts/fields` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listAlertFields`. Every field name the organization's alerts have carried — the built-in alert fields and the labels and annotations integrations sent — most used first, with a recent sample value and the integration kinds that send it. This is what a voice template can refer to. **Permission (API-key scope):** `oncall:alert:read`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:read) - `apiKeyHeader` (oncall:alert:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The fields. Content type: `application/json` Type: `object` - `data` (array of AlertField, required) - `name` (string): The placeholder name, e.g. `alert.title` or a label name. - `builtin` (boolean): A field every alert has, rather than one an integration sends. - `uses` (integer): How often it appeared. - `sample` (string): The value it most recently held. - `sources` (array of string) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/alerts/fields' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/stream-alerts.md # Stream alert changes `GET https://evohub.io/api/v1/alerts/sse` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `streamAlerts`. A Server-Sent Events stream of the organization's alert changes. The stream opens with the comment `: connected` and sends `: keep-alive` every 25 seconds. Each event is one `data:` line holding a JSON object: ``` data: {"type":"alert_ack","alert_id":"…","title":"High error rate on checkout","status":"acknowledged","severity":"critical","org_id":"…"} ``` `type` is `new_alert`, `alert_ack`, `alert_resolved` (also sent when an alert is suppressed), `alert_assigned`, `alert_taken_over` or `alert_redirect`. Events are not replayed: re-read the alert list after reconnecting. **Permission (API-key scope):** `oncall:alert:read`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:read) - `apiKeyHeader` (oncall:alert:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The event stream. Content type: `text/event-stream` Type: `string` ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Streaming is not available right now (plain-text body). ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/alerts/sse' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-incidents.md # List incidents `GET https://evohub.io/api/v1/incidents` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listIncidents`. Lists the organization's incidents, newest first. Paged with `limit` (default 50) and `offset`; there is no total count. **Permission (API-key scope):** `oncall:incident:read`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:read) - `apiKeyHeader` (oncall:incident:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `status` (IncidentStatus, one of `investigating`, `identified`, `monitoring`, `resolved`): Only incidents in this status. - `limit` (integer, default `50`, min 0): Page size. 0 or omitted means 50; more than 500 returns 500. Anything but a whole number of 0 or more is refused (400 `VALIDATION_FAILED`, field `limit`). - `offset` (integer, default `0`, min 0): How many items to skip. Anything but a whole number of 0 or more is refused (400 `VALIDATION_FAILED`, field `offset`). ## Responses ### 200 — A page of incidents. Content type: `application/json` Type: `object` - `data` (array of Incident, required) - `id` (string, required) - `org_id` (string) - `number` (integer, required): Sequential number within the organization. - `title` (string, required) - `summary` (string) - `severity` (IncidentSeverity, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `commander_id` (string) - `started_at` (string (date-time), required) - `identified_at` (string (date-time)) - `resolved_at` (string (date-time)) - `timeline` (array of IncidentTimelineEntry): The timeline entries, oldest first. Only in the answer of Get an incident. - `id` (string) - `incident_id` (string) - `author_id` (string) - `status` (string): The incident's status when the entry was written. - `message` (string) - `created_at` (string (date-time)) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title, summary, severity or commander (not on acknowledge or resolve). Send it with an update to refuse the edit if someone else changed the incident first. - `success` (boolean, required, value `true`) ### 400 — `limit` or `offset` is not a whole number of 0 or more (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/incidents' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/create-incident.md # Declare an incident `POST https://evohub.io/api/v1/incidents` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `createIncident`. Declares an incident. It starts `investigating`, with the caller as commander unless `commander_id` names someone else, who must be a member who can respond to alerts. To record an incident that already ended, send both `started_at` and `resolved_at`: it is stored resolved, with those times. **Permission (API-key scope):** `oncall:incident:write`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:write) - `apiKeyHeader` (oncall:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `IncidentCreate` - `title` (string, required, max length 200): Not blank. - `severity` (IncidentSeverity, required, one of `none`, `minor`, `major`, `critical`) - `summary` (string) - `commander_id` (string): Defaults to the caller. - `started_at` (string (date-time)): With `resolved_at`: record an incident that already ended. - `resolved_at` (string (date-time)): With `started_at`: record an incident that already ended. Must be after `started_at` and not in the future. ## Responses ### 201 — The incident. Content type: `application/json` Type: `object` - `data` (Incident, required) - `id` (string, required) - `org_id` (string) - `number` (integer, required): Sequential number within the organization. - `title` (string, required) - `summary` (string) - `severity` (IncidentSeverity, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `commander_id` (string) - `started_at` (string (date-time), required) - `identified_at` (string (date-time)) - `resolved_at` (string (date-time)) - `timeline` (array of IncidentTimelineEntry): The timeline entries, oldest first. Only in the answer of Get an incident. - `id` (string) - `incident_id` (string) - `author_id` (string) - `status` (string): The incident's status when the entry was written. - `message` (string) - `created_at` (string (date-time)) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title, summary, severity or commander (not on acknowledge or resolve). Send it with an update to refuse the edit if someone else changed the incident first. - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `title` or `severity` is missing, `title` is blank or longer than 200 characters, `severity` is not one of the four values, or the past times are not a start before an end that has already happened (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — `commander_id` is not a member of the organization (`NOT_A_MEMBER`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 422 — The person cannot respond to alerts: they do not hold `oncall:alert:respond` (`CANNOT_RESPOND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Whether the person may be paged could not be checked right now; nothing was changed (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/incidents' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "title": "Checkout payments failing", "summary": "Card payments time out for about 30% of customers.", "severity": "major" }' ``` --- Source: https://docs.evohub.io/oncall/get-incident.md # Get an incident `GET https://evohub.io/api/v1/incidents/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getIncident`. Returns one incident, with its timeline entries in `timeline`. **Permission (API-key scope):** `oncall:incident:read`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:read) - `apiKeyHeader` (oncall:incident:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Responses ### 200 — The incident. Content type: `application/json` Type: `object` - `data` (Incident, required) - `id` (string, required) - `org_id` (string) - `number` (integer, required): Sequential number within the organization. - `title` (string, required) - `summary` (string) - `severity` (IncidentSeverity, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `commander_id` (string) - `started_at` (string (date-time), required) - `identified_at` (string (date-time)) - `resolved_at` (string (date-time)) - `timeline` (array of IncidentTimelineEntry): The timeline entries, oldest first. Only in the answer of Get an incident. - `id` (string) - `incident_id` (string) - `author_id` (string) - `status` (string): The incident's status when the entry was written. - `message` (string) - `created_at` (string (date-time)) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title, summary, severity or commander (not on acknowledge or resolve). Send it with an update to refuse the edit if someone else changed the incident first. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No incident with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/update-incident.md # Update an incident `PUT https://evohub.io/api/v1/incidents/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `updateIncident`. Changes the title, summary, severity or commander. A field left out or empty keeps its value, so a field cannot be cleared this way. `severity` must be one of the four values, and a `commander_id` other than the caller must be a member of the organization who can respond to alerts, as on create. When the incident is published to a status page, a new title — and a changed severity, as the public impact — is relayed there. Send the incident's `version` to make the edit conditional: if someone changed the incident since you read it, nothing is written (409 `EDIT_CONFLICT`). **Permission (API-key scope):** `oncall:incident:write`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:write) - `apiKeyHeader` (oncall:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Request body (required) Content type: `application/json` Type: `IncidentUpdate` Empty or omitted fields keep their value. - `title` (string, max length 200): Not blank; empty keeps the stored title. - `summary` (string) - `severity` (IncidentSeverity, one of `none`, `minor`, `major`, `critical`) - `commander_id` (string) - `version` (integer): The incident's `version` this edit was made against. When it is no longer the current one, nothing is changed (409 `EDIT_CONFLICT`). Omitted, the edit applies to whatever is stored. ## Responses ### 200 — The incident after the change. Content type: `application/json` Type: `object` - `data` (Incident, required) - `id` (string, required) - `org_id` (string) - `number` (integer, required): Sequential number within the organization. - `title` (string, required) - `summary` (string) - `severity` (IncidentSeverity, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `commander_id` (string) - `started_at` (string (date-time), required) - `identified_at` (string (date-time)) - `resolved_at` (string (date-time)) - `timeline` (array of IncidentTimelineEntry): The timeline entries, oldest first. Only in the answer of Get an incident. - `id` (string) - `incident_id` (string) - `author_id` (string) - `status` (string): The incident's status when the entry was written. - `message` (string) - `created_at` (string (date-time)) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title, summary, severity or commander (not on acknowledge or resolve). Send it with an update to refuse the edit if someone else changed the incident first. - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), `title` is blank or longer than 200 characters, or `severity` is not one of the four values (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No incident with this id in your organization (`NOT_FOUND`), or `commander_id` is not a member of the organization (`NOT_A_MEMBER`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — `version` is not the incident's current version; it was changed since you read it (`EDIT_CONFLICT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 422 — The person cannot respond to alerts: they do not hold `oncall:alert:respond` (`CANNOT_RESPOND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Whether the person may be paged could not be checked right now; nothing was changed (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "severity": "critical" }' ``` --- Source: https://docs.evohub.io/oncall/acknowledge-incident.md # Mark an incident identified `POST https://evohub.io/api/v1/incidents/{id}/acknowledge` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `acknowledgeIncident`. Moves the incident to `identified`, adds a timeline entry and, when the incident is published, posts an update to the status page. An incident that is already identified or resolved is returned unchanged. No request body. **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Responses ### 200 — The incident. Content type: `application/json` Type: `object` - `data` (Incident, required) - `id` (string, required) - `org_id` (string) - `number` (integer, required): Sequential number within the organization. - `title` (string, required) - `summary` (string) - `severity` (IncidentSeverity, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `commander_id` (string) - `started_at` (string (date-time), required) - `identified_at` (string (date-time)) - `resolved_at` (string (date-time)) - `timeline` (array of IncidentTimelineEntry): The timeline entries, oldest first. Only in the answer of Get an incident. - `id` (string) - `incident_id` (string) - `author_id` (string) - `status` (string): The incident's status when the entry was written. - `message` (string) - `created_at` (string (date-time)) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title, summary, severity or commander (not on acknowledge or resolve). Send it with an update to refuse the edit if someone else changed the incident first. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No incident with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/acknowledge' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/resolve-incident.md # Resolve an incident `POST https://evohub.io/api/v1/incidents/{id}/resolve` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `resolveIncident`. Resolves the incident, adds a timeline entry and, when the incident is published, resolves it on the status page too. Resolving it again changes nothing. No request body. **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Responses ### 200 — The incident. Content type: `application/json` Type: `object` - `data` (Incident, required) - `id` (string, required) - `org_id` (string) - `number` (integer, required): Sequential number within the organization. - `title` (string, required) - `summary` (string) - `severity` (IncidentSeverity, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `commander_id` (string) - `started_at` (string (date-time), required) - `identified_at` (string (date-time)) - `resolved_at` (string (date-time)) - `timeline` (array of IncidentTimelineEntry): The timeline entries, oldest first. Only in the answer of Get an incident. - `id` (string) - `incident_id` (string) - `author_id` (string) - `status` (string): The incident's status when the entry was written. - `message` (string) - `created_at` (string (date-time)) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every edit of the title, summary, severity or commander (not on acknowledge or resolve). Send it with an update to refuse the edit if someone else changed the incident first. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No incident with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/resolve' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-incident-timeline.md # List an incident's timeline `GET https://evohub.io/api/v1/incidents/{id}/timeline` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listIncidentTimeline`. Returns the incident's timeline entries, oldest first. **Permission (API-key scope):** `oncall:incident:read`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:read) - `apiKeyHeader` (oncall:incident:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Responses ### 200 — The entries. Content type: `application/json` Type: `object` - `data` (array of IncidentTimelineEntry, required) - `id` (string) - `incident_id` (string) - `author_id` (string) - `status` (string): The incident's status when the entry was written. - `message` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No incident with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/timeline' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/add-incident-timeline-entry.md # Add a timeline entry `POST https://evohub.io/api/v1/incidents/{id}/timeline` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `addIncidentTimelineEntry`. Adds an entry to the incident's timeline. When the incident is published to a status page, the entry is posted there as an update as well. **Permission (API-key scope):** `oncall:incident:write`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:write) - `apiKeyHeader` (oncall:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Request body (required) Content type: `application/json` Type: `MessageBody` - `message` (string, required, min length 1) ## Responses ### 201 — The entry. Content type: `application/json` Type: `object` - `data` (IncidentTimelineEntry, required) - `id` (string) - `incident_id` (string) - `author_id` (string) - `status` (string): The incident's status when the entry was written. - `message` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`) or `message` is empty (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No incident with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/timeline' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "message": "Rolled back payments-api to v2.14.3." }' ``` --- Source: https://docs.evohub.io/oncall/delete-incident-timeline-entry.md # Delete a timeline entry `DELETE https://evohub.io/api/v1/incidents/{id}/timeline/{entryID}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `deleteIncidentTimelineEntry`. Removes an entry. When it was posted to a status page, it is removed there too. **Permission (API-key scope):** `oncall:incident:write`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:write) - `apiKeyHeader` (oncall:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. - `entryID` (string, required, example `f0e1d2c3-b4a5-4968-8776-655443322110`): The timeline entry's id. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such entry on this incident (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/timeline/f0e1d2c3-b4a5-4968-8776-655443322110' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/edit-incident-timeline-entry.md # Edit a timeline entry `PATCH https://evohub.io/api/v1/incidents/{id}/timeline/{entryID}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `editIncidentTimelineEntry`. Replaces an entry's message. When the entry was posted to a status page, the edit is relayed there. **Permission (API-key scope):** `oncall:incident:write`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:write) - `apiKeyHeader` (oncall:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. - `entryID` (string, required, example `f0e1d2c3-b4a5-4968-8776-655443322110`): The timeline entry's id. ## Request body (required) Content type: `application/json` Type: `MessageBody` - `message` (string, required, min length 1) ## Responses ### 200 — The entry after the change. Content type: `application/json` Type: `object` - `data` (IncidentTimelineEntry, required) - `id` (string) - `incident_id` (string) - `author_id` (string) - `status` (string): The incident's status when the entry was written. - `message` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`) or `message` is empty (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such entry on this incident (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/timeline/f0e1d2c3-b4a5-4968-8776-655443322110' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "message": "Rolled back payments-api to v2.14.3; error rate is falling." }' ``` --- Source: https://docs.evohub.io/oncall/stream-incident-timeline.md # Stream an incident's timeline `GET https://evohub.io/api/v1/incidents/{id}/sse` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `streamIncidentTimeline`. A Server-Sent Events stream of new entries added to the incident's timeline with the timeline endpoint. It opens with `: connected` and sends `: keep-alive` every 25 seconds. Each event is one `data:` line: ``` data: {"type":"timeline_entry","incident_id":"…","entry_id":"…","author_id":"…","status":"investigating","message":"…","org_id":"…"} ``` Status changes and edits are not streamed; re-read the timeline after reconnecting. **Permission (API-key scope):** `oncall:incident:read`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:read) - `apiKeyHeader` (oncall:incident:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Responses ### 200 — The event stream. Content type: `text/event-stream` Type: `string` ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Streaming is not available right now (plain-text body). ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/sse' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/get-incident-publication.md # Get publishing state `GET https://evohub.io/api/v1/incidents/{id}/publish` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getIncidentPublication`. Whether the incident is published to a status page, and where. An incident that is not published answers `{"published": false}`. **Permission (API-key scope):** `oncall:incident:read`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:read) - `apiKeyHeader` (oncall:incident:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Responses ### 200 — The publishing state. Content type: `application/json` Type: `object` - `data` (IncidentPublication, required) - `published` (boolean, required) - `status_page_id` (string) - `status_incident_id` (string): The incident's id on the status page. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/publish' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/publish-incident.md # Publish to a status page `POST https://evohub.io/api/v1/incidents/{id}/publish` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `publishIncident`. Publishes the incident to one of the organization's status pages. From then on status changes, timeline entries and title or severity edits are relayed to the public incident. `impact` defaults to the incident's severity. `message` is the first public update; it defaults to the incident's summary, or a generic line. **Permission (API-key scope):** `oncall:incident:write`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:write) - `apiKeyHeader` (oncall:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Request body (required) Content type: `application/json` Type: `IncidentPublish` - `page_id` (string, required): The status page to publish to. - `impact` (string, one of `none`, `minor`, `major`, `critical`): Defaults to the incident's severity. - `affected_component_ids` (array of string) - `message` (string): The first public update. ## Responses ### 201 — Published. Content type: `application/json` Type: `object` - `data` (IncidentPublication, required) - `published` (boolean, required) - `status_page_id` (string) - `status_incident_id` (string): The incident's id on the status page. - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), `page_id` is empty, or `impact` is not one of `none`, `minor`, `major`, `critical` (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No incident with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — The incident is already published (`ALREADY_PUBLISHED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 502 — The status page did not accept the incident (`STATUS_PUBLISH_FAILED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/publish' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "impact": "major", "message": "We are investigating failed card payments.", "page_id": "page_1f2e3d4c", "affected_component_ids": [ "comp_payments" ] }' ``` --- Source: https://docs.evohub.io/oncall/unpublish-incident.md # Remove from the status page `DELETE https://evohub.io/api/v1/incidents/{id}/publish` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `unpublishIncident`. Removes the incident from the status page it was published to. **Permission (API-key scope):** `oncall:incident:write`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:write) - `apiKeyHeader` (oncall:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — The incident is not published (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 502 — The status page did not accept the removal (`STATUS_UNPUBLISH_FAILED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/publish' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/get-postmortem.md # Get an incident's postmortem `GET https://evohub.io/api/v1/incidents/{id}/postmortem` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getPostmortem`. Returns the incident's postmortem. An incident without one yet answers 200 with `data: null`. **Permission (API-key scope):** `oncall:postmortem:read`. ## Authorization Any one of: - `bearerAuth` (oncall:postmortem:read) - `apiKeyHeader` (oncall:postmortem:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Responses ### 200 — The postmortem, or null. Content type: `application/json` Type: `object` - `data` (Postmortem | null, required) - One of: - Postmortem - `id` (string) - `org_id` (string) - `incident_id` (string) - `author_id` (string) - `status` (PostmortemStatus, one of `draft`, `in_review`, `published`) - `summary` (string) - `root_cause` (string) - `contributing_factors` (string) - `timeline` (string) - `action_items` (string) - `lessons_learned` (string) - `published_at` (string (date-time)) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - null - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No incident with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/postmortem' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/replace-postmortem.md # Write an incident's postmortem (PUT) `PUT https://evohub.io/api/v1/incidents/{id}/postmortem` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `replacePostmortem`. Creates the incident's postmortem, or replaces every field of the existing one: a field left out is stored empty, except `status`, which keeps its current value (and defaults to `draft` on creation). A `status` other than `draft`, `in_review` or `published` is refused with 400 `VALIDATION_FAILED`. Setting it to `published` records `published_at` the first time and, when the incident is on a status page, posts the postmortem there. The caller becomes the author. POST and PUT do the same thing. **Permission (API-key scope):** `oncall:postmortem:write`. ## Authorization Any one of: - `bearerAuth` (oncall:postmortem:write) - `apiKeyHeader` (oncall:postmortem:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Request body (required) Content type: `application/json` Type: `PostmortemWrite` Every field is written as sent; one left out is stored empty. - `status` (PostmortemStatus, one of `draft`, `in_review`, `published`) - `summary` (string) - `root_cause` (string) - `contributing_factors` (string) - `timeline` (string) - `action_items` (string) - `lessons_learned` (string) ## Responses ### 200 — The postmortem. Content type: `application/json` Type: `object` - `data` (Postmortem, required) - `id` (string) - `org_id` (string) - `incident_id` (string) - `author_id` (string) - `status` (PostmortemStatus, one of `draft`, `in_review`, `published`) - `summary` (string) - `root_cause` (string) - `contributing_factors` (string) - `timeline` (string) - `action_items` (string) - `lessons_learned` (string) - `published_at` (string (date-time)) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or `status` is not `draft`, `in_review` or `published` (`VALIDATION_FAILED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No incident with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/postmortem' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "status": "draft", "summary": "Payments timed out for 42 minutes.", "timeline": "08:01 first alert; 08:43 rollback complete.", "root_cause": "A connection pool limit was lowered in the v2.15.0 release.", "action_items": "Alert on pool saturation; load test pool changes.", "lessons_learned": "Configuration changes need the same review as code.", "contributing_factors": "No alert on pool saturation." }' ``` --- Source: https://docs.evohub.io/oncall/save-postmortem.md # Write an incident's postmortem `POST https://evohub.io/api/v1/incidents/{id}/postmortem` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `savePostmortem`. Creates the incident's postmortem, or replaces every field of the existing one: a field left out is stored empty, except `status`, which keeps its current value (and defaults to `draft` on creation). A `status` other than `draft`, `in_review` or `published` is refused with 400 `VALIDATION_FAILED`. Setting it to `published` records `published_at` the first time and, when the incident is on a status page, posts the postmortem there. The caller becomes the author. POST and PUT do the same thing. **Permission (API-key scope):** `oncall:postmortem:write`. ## Authorization Any one of: - `bearerAuth` (oncall:postmortem:write) - `apiKeyHeader` (oncall:postmortem:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b`): The incident's id. ## Request body (required) Content type: `application/json` Type: `PostmortemWrite` Every field is written as sent; one left out is stored empty. - `status` (PostmortemStatus, one of `draft`, `in_review`, `published`) - `summary` (string) - `root_cause` (string) - `contributing_factors` (string) - `timeline` (string) - `action_items` (string) - `lessons_learned` (string) ## Responses ### 200 — The postmortem. Content type: `application/json` Type: `object` - `data` (Postmortem, required) - `id` (string) - `org_id` (string) - `incident_id` (string) - `author_id` (string) - `status` (PostmortemStatus, one of `draft`, `in_review`, `published`) - `summary` (string) - `root_cause` (string) - `contributing_factors` (string) - `timeline` (string) - `action_items` (string) - `lessons_learned` (string) - `published_at` (string (date-time)) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or `status` is not `draft`, `in_review` or `published` (`VALIDATION_FAILED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No incident with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/incidents/2e7f9a1b-3c4d-4e5f-8a9b-0c1d2e3f4a5b/postmortem' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "status": "draft", "summary": "Payments timed out for 42 minutes.", "timeline": "08:01 first alert; 08:43 rollback complete.", "root_cause": "A connection pool limit was lowered in the v2.15.0 release.", "action_items": "Alert on pool saturation; load test pool changes.", "lessons_learned": "Configuration changes need the same review as code.", "contributing_factors": "No alert on pool saturation." }' ``` --- Source: https://docs.evohub.io/oncall/list-schedules.md # List schedules `GET https://evohub.io/api/v1/schedules` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listSchedules`. Lists the schedules the caller can see: the organization-wide ones and those of the caller's teams. Layers and overrides are not included; get a schedule for them. **Permission (API-key scope):** `oncall:schedule:read`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:read) - `apiKeyHeader` (oncall:schedule:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The schedules. Content type: `application/json` Type: `object` - `data` (array of Schedule, required) - `id` (string) - `org_id` (string) - `team_id` (string): Empty for an organization-wide schedule. - `name` (string) - `description` (string) - `timezone` (string): IANA timezone every shift is evaluated in. - `active` (boolean) - `layers` (array of ScheduleLayer) - `id` (string) - `schedule_id` (string) - `name` (string) - `priority` (integer): Lower numbers win. When several layers have someone on call, the layer with the smallest `priority` decides who is paged. - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_start` (string (date-time)): When the rota's first turn started. - `handoff_day` (integer): For a `custom` rotation, the days (or, with `rotation_unit` `hour`, the hours) each turn lasts. Daily and weekly rotations ignore it. - `handoff_time` (string): `HH:MM` of the handover. - `rotation_unit` (string): What `handoff_day` counts on a `custom` rotation, `day` or `hour`; empty means `day`. - `handoff_weekday` (integer | null): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6; `null` is the weekday `rotation_start` falls on. - `restrict_to_weekdays` (array of integer) - `restrict_start_time` (string): `HH:MM` - `restrict_end_time` (string): `HH:MM` - `rotations` (array of ScheduleRotation) - `active_from` (string (date-time)): Set on a layer that replaces another from a later moment; it applies from then. - `active_until` (string (date-time)): Set on a layer that a change replaces from a later moment; it applies until then. - `replaces_layer_id` (string): On a changed copy, the layer it replaces. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `overrides` (array of ScheduleOverride) - `id` (string) - `schedule_id` (string) - `user_id` (string): Whom the override covers, when recorded. - `override_user_id` (string): Who is on call instead. - `start_time` (string (date-time)) - `end_time` (string (date-time)) - `reason` (string) - `created_at` (string (date-time)) - `external_source` (string): Where it was imported from (`opsgenie`, `pagerduty`); absent for schedules made in EvoHub. - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/schedules' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/create-schedule.md # Create a schedule `POST https://evohub.io/api/v1/schedules` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `createSchedule`. Creates an empty schedule; add layers and people to it next. `timezone` defaults to `UTC` and decides when every shift on the schedule starts and ends. `team_id` defaults to the caller's first team and must be a team the caller is in. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `ScheduleWrite` On create `name` is required; on update an empty field keeps its value. - `name` (string, max length 100) - `description` (string) - `timezone` (string, default `UTC`): IANA name, e.g. `Europe/Istanbul`. `Local` is refused — it names the server's zone, not one a schedule can mean. - `active` (boolean, default `true`) - `team_id` (string): A team the caller is in; defaults to the caller's first team on create. ## Responses ### 201 — The schedule. Content type: `application/json` Type: `object` - `data` (Schedule, required) - `id` (string) - `org_id` (string) - `team_id` (string): Empty for an organization-wide schedule. - `name` (string) - `description` (string) - `timezone` (string): IANA timezone every shift is evaluated in. - `active` (boolean) - `layers` (array of ScheduleLayer) - `id` (string) - `schedule_id` (string) - `name` (string) - `priority` (integer): Lower numbers win. When several layers have someone on call, the layer with the smallest `priority` decides who is paged. - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_start` (string (date-time)): When the rota's first turn started. - `handoff_day` (integer): For a `custom` rotation, the days (or, with `rotation_unit` `hour`, the hours) each turn lasts. Daily and weekly rotations ignore it. - `handoff_time` (string): `HH:MM` of the handover. - `rotation_unit` (string): What `handoff_day` counts on a `custom` rotation, `day` or `hour`; empty means `day`. - `handoff_weekday` (integer | null): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6; `null` is the weekday `rotation_start` falls on. - `restrict_to_weekdays` (array of integer) - `restrict_start_time` (string): `HH:MM` - `restrict_end_time` (string): `HH:MM` - `rotations` (array of ScheduleRotation) - `active_from` (string (date-time)): Set on a layer that replaces another from a later moment; it applies from then. - `active_until` (string (date-time)): Set on a layer that a change replaces from a later moment; it applies until then. - `replaces_layer_id` (string): On a changed copy, the layer it replaces. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `overrides` (array of ScheduleOverride) - `id` (string) - `schedule_id` (string) - `user_id` (string): Whom the override covers, when recorded. - `override_user_id` (string): Who is on call instead. - `start_time` (string (date-time)) - `end_time` (string (date-time)) - `reason` (string) - `created_at` (string (date-time)) - `external_source` (string): Where it was imported from (`opsgenie`, `pagerduty`); absent for schedules made in EvoHub. - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `name` is empty or longer than 100 characters (`VALIDATION_FAILED`); or `timezone` is not an IANA name (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`), or `team_id` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/schedules' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Platform primary", "team_id": "a3f9c2e1-7b4d-4c8a-9e6f-2d1b0c9a8f7e", "timezone": "Europe/Istanbul", "description": "Weekly rota for the platform team" }' ``` --- Source: https://docs.evohub.io/oncall/get-schedule.md # Get a schedule `GET https://evohub.io/api/v1/schedules/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getSchedule`. Returns the schedule with its layers, each layer's people in rota order, and its overrides. A schedule that belongs to a team is visible only to callers in that team (and to administrators acting in person); others get 404, as if it did not exist. **Permission (API-key scope):** `oncall:schedule:read`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:read) - `apiKeyHeader` (oncall:schedule:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. ## Responses ### 200 — The schedule. Content type: `application/json` Type: `object` - `data` (Schedule, required) - `id` (string) - `org_id` (string) - `team_id` (string): Empty for an organization-wide schedule. - `name` (string) - `description` (string) - `timezone` (string): IANA timezone every shift is evaluated in. - `active` (boolean) - `layers` (array of ScheduleLayer) - `id` (string) - `schedule_id` (string) - `name` (string) - `priority` (integer): Lower numbers win. When several layers have someone on call, the layer with the smallest `priority` decides who is paged. - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_start` (string (date-time)): When the rota's first turn started. - `handoff_day` (integer): For a `custom` rotation, the days (or, with `rotation_unit` `hour`, the hours) each turn lasts. Daily and weekly rotations ignore it. - `handoff_time` (string): `HH:MM` of the handover. - `rotation_unit` (string): What `handoff_day` counts on a `custom` rotation, `day` or `hour`; empty means `day`. - `handoff_weekday` (integer | null): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6; `null` is the weekday `rotation_start` falls on. - `restrict_to_weekdays` (array of integer) - `restrict_start_time` (string): `HH:MM` - `restrict_end_time` (string): `HH:MM` - `rotations` (array of ScheduleRotation) - `active_from` (string (date-time)): Set on a layer that replaces another from a later moment; it applies from then. - `active_until` (string (date-time)): Set on a layer that a change replaces from a later moment; it applies until then. - `replaces_layer_id` (string): On a changed copy, the layer it replaces. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `overrides` (array of ScheduleOverride) - `id` (string) - `schedule_id` (string) - `user_id` (string): Whom the override covers, when recorded. - `override_user_id` (string): Who is on call instead. - `start_time` (string (date-time)) - `end_time` (string (date-time)) - `reason` (string) - `created_at` (string (date-time)) - `external_source` (string): Where it was imported from (`opsgenie`, `pagerduty`); absent for schedules made in EvoHub. - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No schedule with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/update-schedule.md # Update a schedule `PUT https://evohub.io/api/v1/schedules/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `updateSchedule`. Changes the schedule. A field left out or empty keeps its value; `active` changes only when sent. A schedule that belongs to a team is visible only to callers in that team (and to administrators acting in person); others get 404, as if it did not exist. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. ## Request body (required) Content type: `application/json` Type: `ScheduleWrite` On create `name` is required; on update an empty field keeps its value. - `name` (string, max length 100) - `description` (string) - `timezone` (string, default `UTC`): IANA name, e.g. `Europe/Istanbul`. `Local` is refused — it names the server's zone, not one a schedule can mean. - `active` (boolean, default `true`) - `team_id` (string): A team the caller is in; defaults to the caller's first team on create. ## Responses ### 200 — The schedule after the change. Content type: `application/json` Type: `object` - `data` (Schedule, required) - `id` (string) - `org_id` (string) - `team_id` (string): Empty for an organization-wide schedule. - `name` (string) - `description` (string) - `timezone` (string): IANA timezone every shift is evaluated in. - `active` (boolean) - `layers` (array of ScheduleLayer) - `id` (string) - `schedule_id` (string) - `name` (string) - `priority` (integer): Lower numbers win. When several layers have someone on call, the layer with the smallest `priority` decides who is paged. - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_start` (string (date-time)): When the rota's first turn started. - `handoff_day` (integer): For a `custom` rotation, the days (or, with `rotation_unit` `hour`, the hours) each turn lasts. Daily and weekly rotations ignore it. - `handoff_time` (string): `HH:MM` of the handover. - `rotation_unit` (string): What `handoff_day` counts on a `custom` rotation, `day` or `hour`; empty means `day`. - `handoff_weekday` (integer | null): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6; `null` is the weekday `rotation_start` falls on. - `restrict_to_weekdays` (array of integer) - `restrict_start_time` (string): `HH:MM` - `restrict_end_time` (string): `HH:MM` - `rotations` (array of ScheduleRotation) - `active_from` (string (date-time)): Set on a layer that replaces another from a later moment; it applies from then. - `active_until` (string (date-time)): Set on a layer that a change replaces from a later moment; it applies until then. - `replaces_layer_id` (string): On a changed copy, the layer it replaces. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `overrides` (array of ScheduleOverride) - `id` (string) - `schedule_id` (string) - `user_id` (string): Whom the override covers, when recorded. - `override_user_id` (string): Who is on call instead. - `start_time` (string (date-time)) - `end_time` (string (date-time)) - `reason` (string) - `created_at` (string (date-time)) - `external_source` (string): Where it was imported from (`opsgenie`, `pagerduty`); absent for schedules made in EvoHub. - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `name` is blank or longer than 100 characters (`VALIDATION_FAILED`); or `timezone` is not an IANA name (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`), or `team_id` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No schedule with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "active": false }' ``` --- Source: https://docs.evohub.io/oncall/delete-schedule.md # Delete a schedule `DELETE https://evohub.io/api/v1/schedules/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `deleteSchedule`. Deletes the schedule with its layers, rotations and overrides. A schedule that belongs to a team is visible only to callers in that team (and to administrators acting in person); others get 404, as if it did not exist. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No schedule with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/get-schedule-on-call-now.md # Who is on call on a schedule `GET https://evohub.io/api/v1/schedules/{id}/on-call-now` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getScheduleOnCallNow`. Who is on call on this schedule right now, with an override taken into account, and when their shift started and ends. When nobody is, `data` is `{"on_call": null}`. A schedule that belongs to a team is visible only to callers in that team (and to administrators acting in person); others get 404, as if it did not exist. **Permission (API-key scope):** `oncall:schedule:read`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:read) - `apiKeyHeader` (oncall:schedule:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. ## Responses ### 200 — The person on call, or `{"on_call": null}`. Content type: `application/json` Type: `object` - `data` (OnCallInfo | object, required) - One of: - OnCallInfo - `user_id` (string, required) - `schedule_id` (string, required) - `layer_id` (string): The layer giving the shift; empty when an override puts the person on call. - `starts_at` (string (date-time)): Start of the current shift (of the override, when one applies). - `ends_at` (string (date-time)): End of the current shift. - object - `on_call` (null, required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No schedule with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/on-call-now' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-on-call-now.md # Who is on call everywhere `GET https://evohub.io/api/v1/on-call-now` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listOnCallNow`. Who is on call right now on every schedule of the organization that has someone on call. Schedules with nobody on call are left out. **Permission (API-key scope):** `oncall:schedule:read`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:read) - `apiKeyHeader` (oncall:schedule:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — One entry per schedule with someone on call. Content type: `application/json` Type: `object` - `data` (array of OnCallInfo, required) - `user_id` (string, required) - `schedule_id` (string, required) - `layer_id` (string): The layer giving the shift; empty when an override puts the person on call. - `starts_at` (string (date-time)): Start of the current shift (of the override, when one applies). - `ends_at` (string (date-time)): End of the current shift. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/on-call-now' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-schedule-shifts.md # List a schedule's shifts over a range `GET https://evohub.io/api/v1/schedules/{id}/shifts` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listScheduleShifts`. Every layer's turns between `from` and `to`, cut to each layer's shift window (its weekdays and hours), as the calendar shows them. These are the turns the layers give; overrides are not applied here — list them with `GET /api/v1/schedules/{id}/overrides`. Turns are cut at `from` and `to`, and a layer with nobody on it gives none. The range may be at most 62 days. A schedule that belongs to a team is visible only to callers in that team (and to administrators acting in person); others get 404, as if it did not exist. **Permission (API-key scope):** `oncall:schedule:read`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:read) - `apiKeyHeader` (oncall:schedule:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. ## Query parameters - `from` (string (date-time), required, example `2026-10-19T00:00:00Z`): Start of the range, an RFC 3339 instant. - `to` (string (date-time), required, example `2026-11-16T00:00:00Z`): End of the range, an RFC 3339 instant after `from` and at most 62 days later. ## Responses ### 200 — The turns, every layer's together; an empty array when there are none. Content type: `application/json` Type: `object` - `data` (array of ScheduleTurn, required) - `layer_id` (string): The layer giving the turn (`preview` for a layer that is not stored yet). - `user_id` (string): Who holds the turn; empty in a preview with no members. - `start` (string (date-time)) - `end` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — `from` or `to` is missing or not RFC 3339, or `to` is not after `from` or is more than 62 days later (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No schedule with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/shifts?from=2026-10-19T00%3A00%3A00Z&to=2026-11-16T00%3A00%3A00Z' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/get-my-on-call.md # My on-call shifts `GET https://evohub.io/api/v1/schedules/my-on-call` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getMyOnCall`. For the caller: the schedules they are on call for right now (with who takes over next), and their next shift within the coming 14 days, or `null`. Every schedule in the organization is considered. Meant for a person; with an organization API key the key is the caller and holds no shifts. **Permission (API-key scope):** `oncall:schedule:read`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:read) - `apiKeyHeader` (oncall:schedule:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The caller's shifts. Content type: `application/json` Type: `object` - `data` (MyOnCall, required) - `now` (array of object) - `schedule_id` (string) - `schedule_name` (string) - `team_id` (string) - `starts_at` (string (date-time)) - `ends_at` (string (date-time)) - `next_user_id` (string): Who takes over when the shift ends. - `next` (object | null): The caller's next shift within 14 days, or null. - One of: - object - `schedule_id` (string) - `schedule_name` (string) - `starts_at` (string (date-time)) - `ends_at` (string (date-time)) - null - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/schedules/my-on-call' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/create-schedule-layer.md # Add a layer `POST https://evohub.io/api/v1/schedules/{id}/layers` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `createScheduleLayer`. Adds a rotation layer. A layer runs its people in turn, handing over every day, week or `handoff_day` days (`custom`) from `rotation_start` (default: now). Handovers fall on calendar days at `handoff_time` — or, without one, at the time of day `rotation_start` falls — on the schedule's clock, so a rota keeps its local handover time through daylight-saving changes. A handover time the clocks skip happens when they jump; one they pass twice happens the first time. Restrict it to a shift — certain weekdays and a time window in the schedule's timezone — with the three `restrict_*` fields; a window may cross midnight. Leave them out for a layer that covers all day, every day. A schedule that belongs to a team is visible only to callers in that team (and to administrators acting in person); others get 404, as if it did not exist. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. ## Request body (required) Content type: `application/json` Type: `ScheduleLayerWrite` On create `name` and `rotation_type` are required. - `name` (string) - `priority` (integer) - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_start` (string (date-time)): RFC 3339. Defaults to now on create. - `handoff_day` (integer, min 0): For a `custom` rotation, the days (1–365) or, with `rotation_unit` `hour`, the hours (1–168) each turn lasts. - `rotation_unit` (string, one of `day`, `hour`): What `handoff_day` counts on a `custom` rotation; `day` when left out. - `handoff_weekday` (integer, min 0, max 6): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6. - `handoff_time` (string, example `09:00`): `HH:MM`, 00:00–23:59, on the schedule's clock. - `restrict_to_weekdays` (array of integer): 0 is Sunday. - `restrict_start_time` (string): `HH:MM`; give the end too. Empty clears the window. - `restrict_end_time` (string): `HH:MM`; may be earlier than the start for a shift across midnight. ## Responses ### 201 — The layer. Content type: `application/json` Type: `object` - `data` (ScheduleLayer, required) - `id` (string) - `schedule_id` (string) - `name` (string) - `priority` (integer): Lower numbers win. When several layers have someone on call, the layer with the smallest `priority` decides who is paged. - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_start` (string (date-time)): When the rota's first turn started. - `handoff_day` (integer): For a `custom` rotation, the days (or, with `rotation_unit` `hour`, the hours) each turn lasts. Daily and weekly rotations ignore it. - `handoff_time` (string): `HH:MM` of the handover. - `rotation_unit` (string): What `handoff_day` counts on a `custom` rotation, `day` or `hour`; empty means `day`. - `handoff_weekday` (integer | null): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6; `null` is the weekday `rotation_start` falls on. - `restrict_to_weekdays` (array of integer) - `restrict_start_time` (string): `HH:MM` - `restrict_end_time` (string): `HH:MM` - `rotations` (array of ScheduleRotation) - `id` (string) - `layer_id` (string) - `user_id` (string) - `position` (integer): 0-based place in the rota. - `created_at` (string (date-time)) - `active_from` (string (date-time)): Set on a layer that replaces another from a later moment; it applies from then. - `active_until` (string (date-time)): Set on a layer that a change replaces from a later moment; it applies until then. - `replaces_layer_id` (string): On a changed copy, the layer it replaces. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `name` or `rotation_type` is missing, `rotation_type` is not `daily`, `weekly` or `custom`, `handoff_time` is not `HH:MM`, `rotation_start` is not RFC 3339, `handoff_day` is negative (or, on a `custom` rotation, outside 1–365 days or 1–168 hours), `rotation_unit` is not `day` or `hour`, `handoff_weekday` or a weekday is outside 0–6, or the shift window is incomplete or malformed (`VALIDATION_FAILED`, one detail per field). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No schedule with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/layers' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Business hours", "handoff_day": 1, "handoff_time": "09:00", "rotation_type": "weekly", "rotation_start": "2026-09-01T06:00:00Z", "restrict_end_time": "18:00", "restrict_start_time": "09:00", "restrict_to_weekdays": [ 1, 2, 3, 4, 5 ] }' ``` --- Source: https://docs.evohub.io/oncall/preview-schedule-layer.md # Preview a layer before adding it `POST https://evohub.io/api/v1/schedules/{id}/layers/preview` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `previewScheduleLayer`. The next six turns a layer would give, worked out on the schedule's clock, without storing anything. Send the layer as you would to `POST /api/v1/schedules/{id}/layers`, plus `user_ids`, the members in rota order. `name` may be left out. With no `user_ids` the handovers are still returned, each with an empty `user_id`. The turns start from `rotation_start` when it lies in the future, otherwise from now. Each turn runs from handover to handover; it is not cut to the shift window. A schedule that belongs to a team is visible only to callers in that team (and to administrators acting in person); others get 404, as if it did not exist. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. ## Request body (required) Content type: `application/json` Type: `ScheduleLayerSpec` A whole layer as a form sends it — its settings and its members in rota order. - `name` (string) - `priority` (integer) - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_unit` (string, one of `day`, `hour`): What `handoff_day` counts on a `custom` rotation; `day` when left out. - `rotation_start` (string (date-time)) - `handoff_day` (integer): For a `custom` rotation, the days (1–365) or hours (1–168) each turn lasts. - `handoff_weekday` (integer, min 0, max 6): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6. Left out, it hands over on the weekday `rotation_start` falls on. - `handoff_time` (string): `HH:MM` on the schedule's clock. - `restrict_to_weekdays` (array of integer) - `restrict_start_time` (string) - `restrict_end_time` (string) - `user_ids` (array of string): The members in rota order, each once. ## Responses ### 200 — The next six turns. Content type: `application/json` Type: `object` - `data` (object, required) - `turns` (array of ScheduleTurn, required) - `layer_id` (string): The layer giving the turn (`preview` for a layer that is not stored yet). - `user_id` (string): Who holds the turn; empty in a preview with no members. - `start` (string (date-time)) - `end` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or a field is invalid as on create, or `user_ids` names someone twice or holds an empty id (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No schedule with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/layers/preview' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "user_ids": [ "0b8e6c1a-2f4d-4e9b-8a7c-3d5e6f708192", "9a7b5c3d-1e2f-4a6b-8c0d-2e4f6a8b0c1d" ], "handoff_time": "09:00", "rotation_type": "weekly", "rotation_start": "2026-10-19T06:00:00Z", "handoff_weekday": 1 }' ``` --- Source: https://docs.evohub.io/oncall/delete-schedule-layer.md # Delete a layer `DELETE https://evohub.io/api/v1/schedules/{id}/layers/{layerID}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `deleteScheduleLayer`. Deletes a layer and the people on it. Deleting a changed copy that has not taken over yet (one with `replaces_layer_id` and a future `active_from`) cancels that change instead: the layer it was to replace runs on as before. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. - `layerID` (string, required, example `7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f`): The layer's id. It must belong to the schedule in the path. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such schedule, or no such layer on it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/layers/7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/update-schedule-layer.md # Update a layer `PATCH https://evohub.io/api/v1/schedules/{id}/layers/{layerID}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `updateScheduleLayer`. Changes a layer. `name`, `priority`, `rotation_type`, `rotation_start` and `handoff_time` keep their value when left out or empty (so `priority` cannot be set back to 0 here), and so does `rotation_unit`. `handoff_day` keeps its value when left out and is written when sent, 0 included; `handoff_weekday` is written when sent and cannot be cleared. The `restrict_*` fields change only when sent; send empty strings for both times (and an empty weekday list) to turn a shift back into an all-day layer. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. - `layerID` (string, required, example `7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f`): The layer's id. It must belong to the schedule in the path. ## Request body (required) Content type: `application/json` Type: `ScheduleLayerWrite` On create `name` and `rotation_type` are required. - `name` (string) - `priority` (integer) - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_start` (string (date-time)): RFC 3339. Defaults to now on create. - `handoff_day` (integer, min 0): For a `custom` rotation, the days (1–365) or, with `rotation_unit` `hour`, the hours (1–168) each turn lasts. - `rotation_unit` (string, one of `day`, `hour`): What `handoff_day` counts on a `custom` rotation; `day` when left out. - `handoff_weekday` (integer, min 0, max 6): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6. - `handoff_time` (string, example `09:00`): `HH:MM`, 00:00–23:59, on the schedule's clock. - `restrict_to_weekdays` (array of integer): 0 is Sunday. - `restrict_start_time` (string): `HH:MM`; give the end too. Empty clears the window. - `restrict_end_time` (string): `HH:MM`; may be earlier than the start for a shift across midnight. ## Responses ### 200 — The layer after the change. Content type: `application/json` Type: `object` - `data` (ScheduleLayer, required) - `id` (string) - `schedule_id` (string) - `name` (string) - `priority` (integer): Lower numbers win. When several layers have someone on call, the layer with the smallest `priority` decides who is paged. - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_start` (string (date-time)): When the rota's first turn started. - `handoff_day` (integer): For a `custom` rotation, the days (or, with `rotation_unit` `hour`, the hours) each turn lasts. Daily and weekly rotations ignore it. - `handoff_time` (string): `HH:MM` of the handover. - `rotation_unit` (string): What `handoff_day` counts on a `custom` rotation, `day` or `hour`; empty means `day`. - `handoff_weekday` (integer | null): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6; `null` is the weekday `rotation_start` falls on. - `restrict_to_weekdays` (array of integer) - `restrict_start_time` (string): `HH:MM` - `restrict_end_time` (string): `HH:MM` - `rotations` (array of ScheduleRotation) - `id` (string) - `layer_id` (string) - `user_id` (string) - `position` (integer): 0-based place in the rota. - `created_at` (string (date-time)) - `active_from` (string (date-time)): Set on a layer that replaces another from a later moment; it applies from then. - `active_until` (string (date-time)): Set on a layer that a change replaces from a later moment; it applies until then. - `replaces_layer_id` (string): On a changed copy, the layer it replaces. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); a sent `rotation_type`, `handoff_time`, `rotation_start`, `handoff_day`, `rotation_unit` or `handoff_weekday` is invalid as on create, or a weekday or the shift window is invalid (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such schedule, or no such layer on it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/layers/7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "handoff_day": 1, "restrict_end_time": "", "restrict_start_time": "", "restrict_to_weekdays": [] }' ``` --- Source: https://docs.evohub.io/oncall/revise-schedule-layer.md # Change a layer from a later moment `POST https://evohub.io/api/v1/schedules/{id}/layers/{layerID}/revise` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `reviseScheduleLayer`. Changes a layer — its settings and its members — from a moment you choose, so the turn under way is not cut short unless you ask for that. Send the whole layer as it should be (the fields of `POST /api/v1/schedules/{id}/layers`, `name` and `rotation_type` required), `user_ids` with every member in rota order, and when the change takes over: - `effective: now` — at once. - `effective: after_current_shift` (the default) — when the current turn ends. - `effective: after_full_rotation` — when the current pass through the list ends. - `effective: at` with `at`, an RFC 3339 instant that is not in the past. The layer as it was runs until that moment, and a revised copy runs from it: the response's `layer` is the copy, with `active_from` set and `replaces_layer_id` pointing at the original. Unless `restart` is `true`, the person who would hold the rota at that moment keeps it, wherever they now sit in the list; with `restart: true` the new list begins at its first member. Only the layer in effect now can be changed this way; a change already waiting on it is replaced by the new one. Anyone added to the rota must be a member of the organization who can respond to alerts. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. - `layerID` (string, required, example `7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f`): The layer's id. It must belong to the schedule in the path. ## Request body (required) Content type: `application/json` Type: `ScheduleLayerRevision` A layer as it should be, and when that takes over. `name` and `rotation_type` are required. `rotation_start` is checked but not used; the changed layer starts at the moment the change takes over. - `name` (string) - `priority` (integer) - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_unit` (string, one of `day`, `hour`): What `handoff_day` counts on a `custom` rotation; `day` when left out. - `rotation_start` (string (date-time)) - `handoff_day` (integer): For a `custom` rotation, the days (1–365) or hours (1–168) each turn lasts. - `handoff_weekday` (integer, min 0, max 6): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6. Left out, it hands over on the weekday `rotation_start` falls on. - `handoff_time` (string): `HH:MM` on the schedule's clock. - `restrict_to_weekdays` (array of integer) - `restrict_start_time` (string) - `restrict_end_time` (string) - `user_ids` (array of string): The members in rota order, each once. - `effective` (string, one of `now`, `after_current_shift`, `after_full_rotation`, `at`, default `after_current_shift`) - `at` (string (date-time)): With `effective` `at`, the moment the change takes over; not in the past. - `restart` (boolean, default `false`): Begin the new list at its first member instead of keeping the current holder. ## Responses ### 201 — When the change takes over, and the layer that runs from then. Content type: `application/json` Type: `object` - `data` (object, required) - `effective_at` (string (date-time), required) - `layer` (ScheduleLayer, required) - `id` (string) - `schedule_id` (string) - `name` (string) - `priority` (integer): Lower numbers win. When several layers have someone on call, the layer with the smallest `priority` decides who is paged. - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_start` (string (date-time)): When the rota's first turn started. - `handoff_day` (integer): For a `custom` rotation, the days (or, with `rotation_unit` `hour`, the hours) each turn lasts. Daily and weekly rotations ignore it. - `handoff_time` (string): `HH:MM` of the handover. - `rotation_unit` (string): What `handoff_day` counts on a `custom` rotation, `day` or `hour`; empty means `day`. - `handoff_weekday` (integer | null): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6; `null` is the weekday `rotation_start` falls on. - `restrict_to_weekdays` (array of integer) - `restrict_start_time` (string): `HH:MM` - `restrict_end_time` (string): `HH:MM` - `rotations` (array of ScheduleRotation) - `id` (string) - `layer_id` (string) - `user_id` (string) - `position` (integer): 0-based place in the rota. - `created_at` (string (date-time)) - `active_from` (string (date-time)): Set on a layer that replaces another from a later moment; it applies from then. - `active_until` (string (date-time)): Set on a layer that a change replaces from a later moment; it applies until then. - `replaces_layer_id` (string): On a changed copy, the layer it replaces. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); a layer field is invalid as on create, `user_ids` names someone twice, `effective` is not one of the four values, `at` is missing or not RFC 3339 when `effective` is `at`, or `at` is in the past (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such schedule, or no such layer on it (`NOT_FOUND`), or someone added in `user_ids` is not a member of the organization (`NOT_A_MEMBER`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — The layer has ended or has not started yet (`LAYER_NOT_IN_EFFECT`); change the layer in effect instead. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 422 — The person cannot respond to alerts: they do not hold `oncall:alert:respond` (`CANNOT_RESPOND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Whether the person may be paged could not be checked right now; nothing was changed (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/layers/7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f/revise' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Primary", "priority": 1, "user_ids": [ "0b8e6c1a-2f4d-4e9b-8a7c-3d5e6f708192", "9a7b5c3d-1e2f-4a6b-8c0d-2e4f6a8b0c1d", "3c5e7a9b-1d2f-4b6c-8e0a-4f6b8d0e2a3c" ], "effective": "after_current_shift", "handoff_time": "09:00", "rotation_type": "weekly", "handoff_weekday": 1 }' ``` --- Source: https://docs.evohub.io/oncall/preview-schedule-layer-revision.md # Preview a change to a layer `POST https://evohub.io/api/v1/schedules/{id}/layers/{layerID}/revise/preview` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `previewScheduleLayerRevision`. What `POST /api/v1/schedules/{id}/layers/{layerID}/revise` would do with the same body, without storing anything: when the change would take over, and the next six turns — the layer as it stands until then, the changed layer after. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. - `layerID` (string, required, example `7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f`): The layer's id. It must belong to the schedule in the path. ## Request body (required) Content type: `application/json` Type: `ScheduleLayerRevision` A layer as it should be, and when that takes over. `name` and `rotation_type` are required. `rotation_start` is checked but not used; the changed layer starts at the moment the change takes over. - `name` (string) - `priority` (integer) - `rotation_type` (RotationType, one of `daily`, `weekly`, `custom`) - `rotation_unit` (string, one of `day`, `hour`): What `handoff_day` counts on a `custom` rotation; `day` when left out. - `rotation_start` (string (date-time)) - `handoff_day` (integer): For a `custom` rotation, the days (1–365) or hours (1–168) each turn lasts. - `handoff_weekday` (integer, min 0, max 6): The weekday a `weekly` rotation hands over on, 0 (Sunday) to 6. Left out, it hands over on the weekday `rotation_start` falls on. - `handoff_time` (string): `HH:MM` on the schedule's clock. - `restrict_to_weekdays` (array of integer) - `restrict_start_time` (string) - `restrict_end_time` (string) - `user_ids` (array of string): The members in rota order, each once. - `effective` (string, one of `now`, `after_current_shift`, `after_full_rotation`, `at`, default `after_current_shift`) - `at` (string (date-time)): With `effective` `at`, the moment the change takes over; not in the past. - `restart` (boolean, default `false`): Begin the new list at its first member instead of keeping the current holder. ## Responses ### 200 — When the change would take over, and the next six turns. Content type: `application/json` Type: `object` - `data` (object, required) - `effective_at` (string (date-time), required) - `turns` (array of ScheduleTurn, required) - `layer_id` (string): The layer giving the turn (`preview` for a layer that is not stored yet). - `user_id` (string): Who holds the turn; empty in a preview with no members. - `start` (string (date-time)) - `end` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is invalid as for the change itself (`INVALID_BODY`, `VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such schedule, or no such layer on it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — The layer has ended or has not started yet (`LAYER_NOT_IN_EFFECT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/layers/7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f/revise/preview' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Primary", "user_ids": [ "0b8e6c1a-2f4d-4e9b-8a7c-3d5e6f708192", "9a7b5c3d-1e2f-4a6b-8c0d-2e4f6a8b0c1d" ], "effective": "after_full_rotation", "handoff_time": "09:00", "rotation_type": "weekly", "handoff_weekday": 1 }' ``` --- Source: https://docs.evohub.io/oncall/reorder-schedule-rotations.md # Reorder a layer's rota `PUT https://evohub.io/api/v1/schedules/{id}/layers/{layerID}/rotations` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `reorderScheduleRotations`. Sets the order the layer's people run in. `rotation_ids` must list every rota slot of the layer exactly once, in the new order. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. - `layerID` (string, required, example `7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f`): The layer's id. It must belong to the schedule in the path. ## Request body (required) Content type: `application/json` Type: `object` - `rotation_ids` (array of string, required, min items 1) ## Responses ### 200 — The layer's rota in its new order. Content type: `application/json` Type: `object` - `data` (array of ScheduleRotation, required) - `id` (string) - `layer_id` (string) - `user_id` (string) - `position` (integer): 0-based place in the rota. - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or `rotation_ids` is empty or does not list exactly this layer's slots once each (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such schedule, or no such layer on it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/layers/7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f/rotations' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "rotation_ids": [ "8d4e0f2a-3b5c-4d7e-9f1a-2b3c4d5e6f70", "9e5f1a3b-4c6d-4e8f-a0b2-c4d6e8f0a2b4" ] }' ``` --- Source: https://docs.evohub.io/oncall/add-schedule-rotation.md # Add a person to a layer `POST https://evohub.io/api/v1/schedules/{id}/layers/{layerID}/rotations` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `addScheduleRotation`. Adds a person to the layer's rota at `position` (0 is first); without `position` they go to the end. The person must be a member of the organization who can respond to alerts. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. - `layerID` (string, required, example `7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f`): The layer's id. It must belong to the schedule in the path. ## Request body (required) Content type: `application/json` Type: `object` - `user_id` (string, required) - `position` (integer, min 0): 0-based place in the rota; the end when omitted. ## Responses ### 201 — The rota slot. Content type: `application/json` Type: `object` - `data` (ScheduleRotation, required) - `id` (string) - `layer_id` (string) - `user_id` (string) - `position` (integer): 0-based place in the rota. - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`) or `user_id` is empty (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such schedule or layer (`NOT_FOUND`), or the person is not a member of the organization (`NOT_A_MEMBER`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 422 — The person cannot respond to alerts: they do not hold `oncall:alert:respond` (`CANNOT_RESPOND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Whether the person may be paged could not be checked right now; nothing was changed (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/layers/7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f/rotations' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "user_id": "4c2a9e7b-1d3f-4a8c-b6e5-9f0a1b2c3d4e" }' ``` --- Source: https://docs.evohub.io/oncall/delete-schedule-rotation.md # Remove a person from a layer `DELETE https://evohub.io/api/v1/schedules/{id}/layers/{layerID}/rotations/{rotationID}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `deleteScheduleRotation`. Removes one rota slot from the layer. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. - `layerID` (string, required, example `7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f`): The layer's id. It must belong to the schedule in the path. - `rotationID` (string, required, example `8d4e0f2a-3b5c-4d7e-9f1a-2b3c4d5e6f70`): The rotation slot's id. ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such schedule, no such layer on it, or no such slot on the layer (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/layers/7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f/rotations/8d4e0f2a-3b5c-4d7e-9f1a-2b3c4d5e6f70' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-schedule-overrides.md # List overrides `GET https://evohub.io/api/v1/schedules/{id}/overrides` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listScheduleOverrides`. Lists the schedule's overrides. A schedule that belongs to a team is visible only to callers in that team (and to administrators acting in person); others get 404, as if it did not exist. **Permission (API-key scope):** `oncall:schedule:read`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:read) - `apiKeyHeader` (oncall:schedule:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. ## Responses ### 200 — The overrides. Content type: `application/json` Type: `object` - `data` (array of ScheduleOverride, required) - `id` (string) - `schedule_id` (string) - `user_id` (string): Whom the override covers, when recorded. - `override_user_id` (string): Who is on call instead. - `start_time` (string (date-time)) - `end_time` (string (date-time)) - `reason` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No schedule with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/overrides' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/create-schedule-override.md # Create an override `POST https://evohub.io/api/v1/schedules/{id}/overrides` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `createScheduleOverride`. Puts `override_user_id` on call on this schedule between `start_time` and `end_time`, in place of whoever the layers would have chosen. `user_id` optionally records whom they are covering. The covering person must be a member of the organization who can respond to alerts. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. ## Request body (required) Content type: `application/json` Type: `ScheduleOverrideCreate` - `override_user_id` (string, required) - `start_time` (string (date-time), required) - `end_time` (string (date-time), required) - `user_id` (string): Whom they cover. - `reason` (string) ## Responses ### 201 — The override. Content type: `application/json` Type: `object` - `data` (ScheduleOverride, required) - `id` (string) - `schedule_id` (string) - `user_id` (string): Whom the override covers, when recorded. - `override_user_id` (string): Who is on call instead. - `start_time` (string (date-time)) - `end_time` (string (date-time)) - `reason` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or `override_user_id`, `start_time` or `end_time` is missing, a time is not RFC 3339, or `end_time` is not after `start_time` (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such schedule (`NOT_FOUND`), or the person is not a member of the organization (`NOT_A_MEMBER`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 422 — The person cannot respond to alerts: they do not hold `oncall:alert:respond` (`CANNOT_RESPOND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Whether the person may be paged could not be checked right now; nothing was changed (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/overrides' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "reason": "Covering while Ayşe travels", "user_id": "0b8e6c1a-2f4d-4e9b-8a7c-3d5e6f708192", "end_time": "2026-10-13T09:00:00Z", "start_time": "2026-10-12T18:00:00Z", "override_user_id": "4c2a9e7b-1d3f-4a8c-b6e5-9f0a1b2c3d4e" }' ``` --- Source: https://docs.evohub.io/oncall/delete-schedule-override.md # Delete an override `DELETE https://evohub.io/api/v1/schedules/{id}/overrides/{overrideID}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `deleteScheduleOverride`. Deletes an override. **Permission (API-key scope):** `oncall:schedule:write`. ## Authorization Any one of: - `bearerAuth` (oncall:schedule:write) - `apiKeyHeader` (oncall:schedule:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7`): The schedule's id. - `overrideID` (string, required, example `1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d`): The override's id. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No schedule with this id in your organization, or no such override on it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/schedules/5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7/overrides/1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-escalation-policies.md # List escalation policies `GET https://evohub.io/api/v1/escalation-policies` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listEscalationPolicies`. Lists the policies the caller can see — the organization-wide ones and those of the caller's teams — with their steps. **Permission (API-key scope):** `oncall:escalation:read`. ## Authorization Any one of: - `bearerAuth` (oncall:escalation:read) - `apiKeyHeader` (oncall:escalation:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The policies. Content type: `application/json` Type: `object` - `data` (array of EscalationPolicy, required) - `id` (string) - `org_id` (string) - `team_id` (string) - `name` (string) - `description` (string) - `repeat_count` (integer) - `repeat_delay_minutes` (integer) - `repeat_enabled` (boolean) - `ack_timeout_minutes` (integer): Escalate again when an acknowledged alert stays unresolved this long; 0 is off. - `ack_timeout_repeat` (integer): How many times that may happen per alert. - `ack_timeout_reach` (string, one of ``, `others`, `acker_first`): `others` goes past whoever acknowledged; `acker_first` reminds them once first. Empty means `others`. - `steps` (array of EscalationStep) - `id` (string) - `policy_id` (string) - `step_number` (integer) - `delay_minutes` (integer): Wait before this step. - `target_type` (EscalationTargetType, one of `user`, `schedule`, `webhook`, `slack`) - `target_user_id` (string) - `target_schedule_id` (string) - `target_schedule_layer_id` (string): Page only this layer of the schedule. - `target_webhook_url` (string) - `target_slack_channel_id` (string): A Slack channel this step also posts to. - `target_slack_channel_name` (string) - `target_chat_channel_id` (string): A Microsoft Teams channel this step also posts to. - `notify_method` (NotifyMethod, one of ``, `push`, `call`, `email`, `sms`) - `attempts` (integer): How many times the step notifies before the policy moves on. - `attempt_delay_minutes` (integer) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `external_source` (string) - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/escalation-policies' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/create-escalation-policy.md # Create an escalation policy `POST https://evohub.io/api/v1/escalation-policies` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `createEscalationPolicy`. Creates a policy: who is notified about an alert, in what order, and how often it repeats. `team_id` defaults to the caller's first team and must be a team the caller is in. Rules for the policy and its `steps` (the whole list is replaced): - At least one step. `target_type` is `user`, `schedule`, `webhook` or `slack`. A `user` step needs `target_user_id`, a `schedule` step `target_schedule_id`; a `slack` step (legacy) needs `target_slack_channel_id`. - `delay_minutes` (the wait before the step) is one of 1, 2, 3, 5, 10, 15, 30 or 60; only the first step may be 0, meaning at once. - `attempts` is 1–10 (0 or omitted means 1). With more than one attempt, `attempt_delay_minutes` is one of the same intervals. - `notify_method` is empty (each person's own notification preferences), `push`, `call` or `email`. SMS is not available for new steps; existing SMS steps can be kept when a policy is updated. - A user named by a step must be a member who can respond to alerts. A `target_chat_channel_id` must be one of the organization's Microsoft Teams channels. - With `repeat_enabled`, `repeat_count` is 1–5 and `repeat_delay_minutes` one of the intervals above. - `ack_timeout_minutes` is 0 (off) or 1–1440; when set, `ack_timeout_repeat` is 1–5 and `ack_timeout_reach` is `others` (default) or `acker_first`. **Permission (API-key scope):** `oncall:escalation:write`. ## Authorization Any one of: - `bearerAuth` (oncall:escalation:write) - `apiKeyHeader` (oncall:escalation:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `EscalationPolicyWrite` - `name` (string, required, max length 100): Trimmed; must not be blank. - `description` (string) - `team_id` (string) - `repeat_enabled` (boolean) - `repeat_count` (integer, min 0, max 5) - `repeat_delay_minutes` (integer) - `ack_timeout_minutes` (integer, min 0, max 1440) - `ack_timeout_repeat` (integer, min 0, max 5) - `ack_timeout_reach` (string, one of ``, `others`, `acker_first`) - `steps` (array of EscalationStepWrite, required, min items 1) - `step_number` (integer) - `delay_minutes` (integer): 0 (first step only), 1, 2, 3, 5, 10, 15, 30 or 60. - `target_type` (EscalationTargetType, required, one of `user`, `schedule`, `webhook`, `slack`) - `target_user_id` (string) - `target_schedule_id` (string) - `target_schedule_layer_id` (string) - `target_webhook_url` (string) - `target_slack_channel_id` (string) - `target_slack_channel_name` (string): Only together with `target_slack_channel_id`. - `target_chat_channel_id` (string) - `notify_method` (NotifyMethod, one of ``, `push`, `call`, `email`, `sms`) - `attempts` (integer, min 0, max 10): 1–10; 0 or omitted means 1. - `attempt_delay_minutes` (integer): Needed when `attempts` is more than 1. ## Responses ### 201 — The policy. Content type: `application/json` Type: `object` - `data` (EscalationPolicy, required) - `id` (string) - `org_id` (string) - `team_id` (string) - `name` (string) - `description` (string) - `repeat_count` (integer) - `repeat_delay_minutes` (integer) - `repeat_enabled` (boolean) - `ack_timeout_minutes` (integer): Escalate again when an acknowledged alert stays unresolved this long; 0 is off. - `ack_timeout_repeat` (integer): How many times that may happen per alert. - `ack_timeout_reach` (string, one of ``, `others`, `acker_first`): `others` goes past whoever acknowledged; `acker_first` reminds them once first. Empty means `others`. - `steps` (array of EscalationStep) - `id` (string) - `policy_id` (string) - `step_number` (integer) - `delay_minutes` (integer): Wait before this step. - `target_type` (EscalationTargetType, one of `user`, `schedule`, `webhook`, `slack`) - `target_user_id` (string) - `target_schedule_id` (string) - `target_schedule_layer_id` (string): Page only this layer of the schedule. - `target_webhook_url` (string) - `target_slack_channel_id` (string): A Slack channel this step also posts to. - `target_slack_channel_name` (string) - `target_chat_channel_id` (string): A Microsoft Teams channel this step also posts to. - `notify_method` (NotifyMethod, one of ``, `push`, `call`, `email`, `sms`) - `attempts` (integer): How many times the step notifies before the policy moves on. - `attempt_delay_minutes` (integer) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `external_source` (string) - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or a rule below is broken (`VALIDATION_FAILED`, the field is named in `details`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`), or `team_id` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — A step names someone who is not a member of the organization (`NOT_A_MEMBER`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 422 — A step names a person who cannot respond to alerts (`CANNOT_RESPOND`), or asks for SMS (`NOTIFY_METHOD_UNAVAILABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Whether the person may be paged could not be checked right now; nothing was changed (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/escalation-policies' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Platform critical", "steps": [ { "attempts": 2, "step_number": 1, "target_type": "schedule", "delay_minutes": 0, "target_schedule_id": "5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7", "attempt_delay_minutes": 5 }, { "step_number": 2, "target_type": "user", "delay_minutes": 10, "notify_method": "call", "target_user_id": "4c2a9e7b-1d3f-4a8c-b6e5-9f0a1b2c3d4e" } ], "team_id": "a3f9c2e1-7b4d-4c8a-9e6f-2d1b0c9a8f7e", "description": "Page the rota, then the lead", "repeat_count": 2, "repeat_enabled": true, "ack_timeout_reach": "others", "ack_timeout_repeat": 1, "ack_timeout_minutes": 30, "repeat_delay_minutes": 15 }' ``` --- Source: https://docs.evohub.io/oncall/get-escalation-policy.md # Get an escalation policy `GET https://evohub.io/api/v1/escalation-policies/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getEscalationPolicy`. Returns one policy with its steps. A policy that belongs to a team is visible only to callers in that team (and to administrators acting in person); others get 404, as if it did not exist. **Permission (API-key scope):** `oncall:escalation:read`. ## Authorization Any one of: - `bearerAuth` (oncall:escalation:read) - `apiKeyHeader` (oncall:escalation:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `3f8a0b2c-4d6e-4f8a-b1c3-d5e7f9a1b3c5`): The escalation policy's id. ## Responses ### 200 — The policy. Content type: `application/json` Type: `object` - `data` (EscalationPolicy, required) - `id` (string) - `org_id` (string) - `team_id` (string) - `name` (string) - `description` (string) - `repeat_count` (integer) - `repeat_delay_minutes` (integer) - `repeat_enabled` (boolean) - `ack_timeout_minutes` (integer): Escalate again when an acknowledged alert stays unresolved this long; 0 is off. - `ack_timeout_repeat` (integer): How many times that may happen per alert. - `ack_timeout_reach` (string, one of ``, `others`, `acker_first`): `others` goes past whoever acknowledged; `acker_first` reminds them once first. Empty means `others`. - `steps` (array of EscalationStep) - `id` (string) - `policy_id` (string) - `step_number` (integer) - `delay_minutes` (integer): Wait before this step. - `target_type` (EscalationTargetType, one of `user`, `schedule`, `webhook`, `slack`) - `target_user_id` (string) - `target_schedule_id` (string) - `target_schedule_layer_id` (string): Page only this layer of the schedule. - `target_webhook_url` (string) - `target_slack_channel_id` (string): A Slack channel this step also posts to. - `target_slack_channel_name` (string) - `target_chat_channel_id` (string): A Microsoft Teams channel this step also posts to. - `notify_method` (NotifyMethod, one of ``, `push`, `call`, `email`, `sms`) - `attempts` (integer): How many times the step notifies before the policy moves on. - `attempt_delay_minutes` (integer) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `external_source` (string) - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No escalation policy with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/escalation-policies/3f8a0b2c-4d6e-4f8a-b1c3-d5e7f9a1b3c5' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/update-escalation-policy.md # Update an escalation policy `PUT https://evohub.io/api/v1/escalation-policies/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `updateEscalationPolicy`. Changes the policy's settings and steps. A field left out keeps its value, and so do an empty `name` or `team_id`; any other field sent is written, `description: ""` included. `steps` left out keeps the steps; sent, it replaces the whole list. The repeat and ack-timeout rules below are checked on the policy as it will be after the change. Rules for the policy and its `steps`: - At least one step. `target_type` is `user`, `schedule`, `webhook` or `slack`. A `user` step needs `target_user_id`, a `schedule` step `target_schedule_id`; a `slack` step (legacy) needs `target_slack_channel_id`. - `delay_minutes` (the wait before the step) is one of 1, 2, 3, 5, 10, 15, 30 or 60; only the first step may be 0, meaning at once. - `attempts` is 1–10 (0 or omitted means 1). With more than one attempt, `attempt_delay_minutes` is one of the same intervals. - `notify_method` is empty (each person's own notification preferences), `push`, `call` or `email`. SMS is not available for new steps; existing SMS steps can be kept when a policy is updated. - A user named by a step must be a member who can respond to alerts. A `target_chat_channel_id` must be one of the organization's Microsoft Teams channels. - With `repeat_enabled`, `repeat_count` is 1–5 and `repeat_delay_minutes` one of the intervals above. - `ack_timeout_minutes` is 0 (off) or 1–1440; when set, `ack_timeout_repeat` is 1–5 and `ack_timeout_reach` is `others` (default) or `acker_first`. **Permission (API-key scope):** `oncall:escalation:write`. ## Authorization Any one of: - `bearerAuth` (oncall:escalation:write) - `apiKeyHeader` (oncall:escalation:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `3f8a0b2c-4d6e-4f8a-b1c3-d5e7f9a1b3c5`): The escalation policy's id. ## Request body (required) Content type: `application/json` Type: `EscalationPolicyUpdate` Every field is optional; one left out keeps its value. - `name` (string, max length 100) - `description` (string) - `team_id` (string) - `repeat_enabled` (boolean) - `repeat_count` (integer, min 0, max 5) - `repeat_delay_minutes` (integer) - `ack_timeout_minutes` (integer, min 0, max 1440) - `ack_timeout_repeat` (integer, min 0, max 5) - `ack_timeout_reach` (string, one of ``, `others`, `acker_first`) - `steps` (array of EscalationStepWrite, min items 1) - `step_number` (integer) - `delay_minutes` (integer): 0 (first step only), 1, 2, 3, 5, 10, 15, 30 or 60. - `target_type` (EscalationTargetType, required, one of `user`, `schedule`, `webhook`, `slack`) - `target_user_id` (string) - `target_schedule_id` (string) - `target_schedule_layer_id` (string) - `target_webhook_url` (string) - `target_slack_channel_id` (string) - `target_slack_channel_name` (string): Only together with `target_slack_channel_id`. - `target_chat_channel_id` (string) - `notify_method` (NotifyMethod, one of ``, `push`, `call`, `email`, `sms`) - `attempts` (integer, min 0, max 10): 1–10; 0 or omitted means 1. - `attempt_delay_minutes` (integer): Needed when `attempts` is more than 1. ## Responses ### 200 — The policy after the change. Content type: `application/json` Type: `object` - `data` (EscalationPolicy, required) - `id` (string) - `org_id` (string) - `team_id` (string) - `name` (string) - `description` (string) - `repeat_count` (integer) - `repeat_delay_minutes` (integer) - `repeat_enabled` (boolean) - `ack_timeout_minutes` (integer): Escalate again when an acknowledged alert stays unresolved this long; 0 is off. - `ack_timeout_repeat` (integer): How many times that may happen per alert. - `ack_timeout_reach` (string, one of ``, `others`, `acker_first`): `others` goes past whoever acknowledged; `acker_first` reminds them once first. Empty means `others`. - `steps` (array of EscalationStep) - `id` (string) - `policy_id` (string) - `step_number` (integer) - `delay_minutes` (integer): Wait before this step. - `target_type` (EscalationTargetType, one of `user`, `schedule`, `webhook`, `slack`) - `target_user_id` (string) - `target_schedule_id` (string) - `target_schedule_layer_id` (string): Page only this layer of the schedule. - `target_webhook_url` (string) - `target_slack_channel_id` (string): A Slack channel this step also posts to. - `target_slack_channel_name` (string) - `target_chat_channel_id` (string): A Microsoft Teams channel this step also posts to. - `notify_method` (NotifyMethod, one of ``, `push`, `call`, `email`, `sms`) - `attempts` (integer): How many times the step notifies before the policy moves on. - `attempt_delay_minutes` (integer) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `external_source` (string) - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or a rule below is broken (`VALIDATION_FAILED`, the field is named in `details`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`), or `team_id` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No escalation policy with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 422 — A step names a person who cannot respond to alerts (`CANNOT_RESPOND`), or asks for SMS (`NOTIFY_METHOD_UNAVAILABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Whether the person may be paged could not be checked right now; nothing was changed (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/escalation-policies/3f8a0b2c-4d6e-4f8a-b1c3-d5e7f9a1b3c5' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Platform critical", "steps": [ { "step_number": 1, "target_type": "schedule", "delay_minutes": 0, "target_schedule_id": "5b1c7d2e-8f3a-4b9c-a0d1-e2f3a4b5c6d7" } ], "repeat_enabled": false }' ``` --- Source: https://docs.evohub.io/oncall/delete-escalation-policy.md # Delete an escalation policy `DELETE https://evohub.io/api/v1/escalation-policies/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `deleteEscalationPolicy`. Deletes the policy. A policy that belongs to a team is visible only to callers in that team (and to administrators acting in person); others get 404, as if it did not exist. **Permission (API-key scope):** `oncall:escalation:write`. ## Authorization Any one of: - `bearerAuth` (oncall:escalation:write) - `apiKeyHeader` (oncall:escalation:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `3f8a0b2c-4d6e-4f8a-b1c3-d5e7f9a1b3c5`): The escalation policy's id. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No escalation policy with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/escalation-policies/3f8a0b2c-4d6e-4f8a-b1c3-d5e7f9a1b3c5' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-integrations.md # List integrations `GET https://evohub.io/api/v1/integrations` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listIntegrations`. Lists the integrations the caller can see. An integration carries its ingest key (`api_key`), the credential a monitoring tool sends alerts with — treat these responses as secrets. An integration that belongs to a team is visible only to callers in that team; others get 404. **Permission (API-key scope):** `oncall:integration:read`. ## Authorization Any one of: - `bearerAuth` (oncall:integration:read) - `apiKeyHeader` (oncall:integration:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The integrations. Content type: `application/json` Type: `object` - `data` (array of Integration, required) - `id` (string) - `org_id` (string) - `team_id` (string) - `name` (string) - `type` (string): The integration kind, e.g. `prometheus`, `grafana`, `api`, `webhook`, `email`, `github`. - `api_key` (string): The ingest key. A credential: treat it as a secret. - `email_address` (string): Inbound address, for `email` integrations only. - `enabled` (boolean) - `escalation_policy_id` (string) - `last_event_at` (string (date-time)) - `event_count` (integer) - `signing_secret_configured` (boolean): Whether a signing secret is set. The secret itself is never returned. - `external_source` (string) - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/integrations' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/create-integration.md # Create an integration `POST https://evohub.io/api/v1/integrations` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `createIntegration`. Creates an integration with a new ingest key. Point the monitoring tool at `https://evohub.io/ingest/?key=` (see the Alert ingest API reference). An `email` integration also gets its own inbound address in `email_address`. An integration carries its ingest key (`api_key`), the credential a monitoring tool sends alerts with — treat these responses as secrets. An integration that belongs to a team is visible only to callers in that team; others get 404. **Permission (API-key scope):** `oncall:integration:write`. ## Authorization Any one of: - `bearerAuth` (oncall:integration:write) - `apiKeyHeader` (oncall:integration:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `IntegrationCreate` - `name` (string, required, max length 100) - `type` (string, required, one of `prometheus`, `grafana`, `datadog`, `newrelic`, `cloudwatch`, `azuremonitor`, `dynatrace`, `sentry`, `googlecloud`, `zabbix`, `uptimerobot`, `pingdom`, `site24x7`, `statuscake`, `nagios`, `prtg`, `api`, `cortex`, `opensearch`, `email`, `webhook`, `uptimekuma`, `graylog`, `splunk`, `cloudflare`, `instana`, `netdata`, `eventbridge`, `elastic`, `opmanager`): The integration kind; it decides the ingest URL, `/ingest/` (`webhook` is `/ingest/alerts`). Any other value is refused. - `escalation_policy_id` (string) - `team_id` (string): A team the caller is in; empty for organization-wide. ## Responses ### 201 — The integration, with its key. Content type: `application/json` Type: `object` - `data` (Integration, required) - `id` (string) - `org_id` (string) - `team_id` (string) - `name` (string) - `type` (string): The integration kind, e.g. `prometheus`, `grafana`, `api`, `webhook`, `email`, `github`. - `api_key` (string): The ingest key. A credential: treat it as a secret. - `email_address` (string): Inbound address, for `email` integrations only. - `enabled` (boolean) - `escalation_policy_id` (string) - `last_event_at` (string (date-time)) - `event_count` (integer) - `signing_secret_configured` (boolean): Whether a signing secret is set. The secret itself is never returned. - `external_source` (string) - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `name` is empty or too long, `type` is empty or not a known integration type, or `escalation_policy_id` names no policy the caller can see (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`), or `team_id` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/integrations' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Prometheus prod", "type": "prometheus", "escalation_policy_id": "3f8a0b2c-4d6e-4f8a-b1c3-d5e7f9a1b3c5" }' ``` --- Source: https://docs.evohub.io/oncall/get-integration.md # Get an integration `GET https://evohub.io/api/v1/integrations/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getIntegration`. Returns one integration. An integration carries its ingest key (`api_key`), the credential a monitoring tool sends alerts with — treat these responses as secrets. An integration that belongs to a team is visible only to callers in that team; others get 404. **Permission (API-key scope):** `oncall:integration:read`. ## Authorization Any one of: - `bearerAuth` (oncall:integration:read) - `apiKeyHeader` (oncall:integration:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `int_6e5d4c3b-2a19-4f8e-b7d6-c5b4a3928170`): The integration's id. ## Responses ### 200 — The integration. Content type: `application/json` Type: `object` - `data` (Integration, required) - `id` (string) - `org_id` (string) - `team_id` (string) - `name` (string) - `type` (string): The integration kind, e.g. `prometheus`, `grafana`, `api`, `webhook`, `email`, `github`. - `api_key` (string): The ingest key. A credential: treat it as a secret. - `email_address` (string): Inbound address, for `email` integrations only. - `enabled` (boolean) - `escalation_policy_id` (string) - `last_event_at` (string (date-time)) - `event_count` (integer) - `signing_secret_configured` (boolean): Whether a signing secret is set. The secret itself is never returned. - `external_source` (string) - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such integration in your organization (`INTEGRATION_NOT_FOUND`), or it belongs to a team you are not in (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/integrations/int_6e5d4c3b-2a19-4f8e-b7d6-c5b4a3928170' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/update-integration.md # Update an integration `PUT https://evohub.io/api/v1/integrations/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `updateIntegration`. Changes the integration's name, escalation policy or enabled state. A field left out keeps its value; an empty `escalation_policy_id` removes the policy. The type and the key do not change. An integration carries its ingest key (`api_key`), the credential a monitoring tool sends alerts with — treat these responses as secrets. An integration that belongs to a team is visible only to callers in that team; others get 404. **Permission (API-key scope):** `oncall:integration:write`. ## Authorization Any one of: - `bearerAuth` (oncall:integration:write) - `apiKeyHeader` (oncall:integration:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `int_6e5d4c3b-2a19-4f8e-b7d6-c5b4a3928170`): The integration's id. ## Request body (required) Content type: `application/json` Type: `IntegrationUpdate` Every field is optional; one left out keeps its value. - `name` (string, min length 1, max length 100) - `escalation_policy_id` (string): Empty removes the policy. - `enabled` (boolean) ## Responses ### 200 — The integration after the change. Content type: `application/json` Type: `object` - `data` (Integration, required) - `id` (string) - `org_id` (string) - `team_id` (string) - `name` (string) - `type` (string): The integration kind, e.g. `prometheus`, `grafana`, `api`, `webhook`, `email`, `github`. - `api_key` (string): The ingest key. A credential: treat it as a secret. - `email_address` (string): Inbound address, for `email` integrations only. - `enabled` (boolean) - `escalation_policy_id` (string) - `last_event_at` (string (date-time)) - `event_count` (integer) - `signing_secret_configured` (boolean): Whether a signing secret is set. The secret itself is never returned. - `external_source` (string) - `external_id` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `name` is empty or too long, or a newly set `escalation_policy_id` names no policy the caller can see (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such integration in your organization (`INTEGRATION_NOT_FOUND`), or it belongs to a team you are not in (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/integrations/int_6e5d4c3b-2a19-4f8e-b7d6-c5b4a3928170' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Prometheus prod", "enabled": true, "escalation_policy_id": "3f8a0b2c-4d6e-4f8a-b1c3-d5e7f9a1b3c5" }' ``` --- Source: https://docs.evohub.io/oncall/delete-integration.md # Delete an integration `DELETE https://evohub.io/api/v1/integrations/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `deleteIntegration`. Deletes the integration. Its key stops working at once. An integration carries its ingest key (`api_key`), the credential a monitoring tool sends alerts with — treat these responses as secrets. An integration that belongs to a team is visible only to callers in that team; others get 404. **Permission (API-key scope):** `oncall:integration:write`. ## Authorization Any one of: - `bearerAuth` (oncall:integration:write) - `apiKeyHeader` (oncall:integration:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `int_6e5d4c3b-2a19-4f8e-b7d6-c5b4a3928170`): The integration's id. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such integration in your organization (`INTEGRATION_NOT_FOUND`), or it belongs to a team you are not in (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/integrations/int_6e5d4c3b-2a19-4f8e-b7d6-c5b4a3928170' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/set-integration-signing-secret.md # Set or generate a signing secret `PUT https://evohub.io/api/v1/integrations/{id}/signing-secret` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `setIntegrationSigningSecret`. For `github`, `gitlab`, `jenkins` and `sonarqube` integrations: the secret the tool signs its deliveries with, so EvoHub can reject anything not signed with it. Send `{"secret": "…"}` to store a secret already set in the tool (16–256 printable characters without spaces, at least 8 of them different). Send an empty body or an empty `secret` to have one generated: it is returned once, in `signing_secret`, with `Cache-Control: no-store`, and never again. Either way the previous secret stops working at once. A secret you sent is not echoed. **Permission (API-key scope):** `oncall:integration:write`. ## Authorization Any one of: - `bearerAuth` (oncall:integration:write) - `apiKeyHeader` (oncall:integration:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `int_6e5d4c3b-2a19-4f8e-b7d6-c5b4a3928170`): The integration's id. ## Request body Content type: `application/json` Type: `object` - `secret` (string): The secret to store; empty or omitted to generate one. ## Responses ### 200 — The secret is set. Content type: `application/json` Type: `object` - `data` (SigningSecretResult, required) - `signing_secret_configured` (boolean) - `generated` (boolean) - `signing_secret` (string): Only when generated, and only in this response. - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); the integration's type has no signing secret (`VALIDATION_FAILED`, field `type`); or the secret is not acceptable (`VALIDATION_FAILED`, field `secret`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such integration in your organization (`INTEGRATION_NOT_FOUND`), or it belongs to a team you are not in (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — Signing secrets are not available on this server (`SIGNING_SECRET_UNAVAILABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/integrations/int_6e5d4c3b-2a19-4f8e-b7d6-c5b4a3928170/signing-secret' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{}' ``` --- Source: https://docs.evohub.io/oncall/clear-integration-signing-secret.md # Remove a signing secret `DELETE https://evohub.io/api/v1/integrations/{id}/signing-secret` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `clearIntegrationSigningSecret`. Removes the integration's signing secret; deliveries are no longer checked against one. **Permission (API-key scope):** `oncall:integration:write`. ## Authorization Any one of: - `bearerAuth` (oncall:integration:write) - `apiKeyHeader` (oncall:integration:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `int_6e5d4c3b-2a19-4f8e-b7d6-c5b4a3928170`): The integration's id. ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such integration in your organization (`INTEGRATION_NOT_FOUND`), or it belongs to a team you are not in (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — Signing secrets are not available on this server (`SIGNING_SECRET_UNAVAILABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/integrations/int_6e5d4c3b-2a19-4f8e-b7d6-c5b4a3928170/signing-secret' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-maintenance-windows.md # List maintenance windows `GET https://evohub.io/api/v1/maintenance-windows` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listMaintenanceWindows`. Lists the organization's maintenance windows — scheduled, running and past. With `active=true`, only the windows in effect right now. **Permission (API-key scope):** `oncall:maintenance:read`. ## Authorization Any one of: - `bearerAuth` (oncall:maintenance:read) - `apiKeyHeader` (oncall:maintenance:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `active` (string, value `true`): `true` for only the windows in effect now. ## Responses ### 200 — The windows. Content type: `application/json` Type: `object` - `data` (array of MaintenanceWindow, required) - `id` (string) - `org_id` (string) - `title` (string) - `body` (string): What the maintenance does. - `starts_at` (string (date-time)) - `ends_at` (string (date-time)) - `created_by` (string) - `created_at` (string (date-time)) - `ended_at` (string (date-time)): Set when the window was ended early. - `silence_oncall` (boolean): Whether paging is held back during the window. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/maintenance-windows' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/create-maintenance-window.md # Schedule a maintenance window `POST https://evohub.io/api/v1/maintenance-windows` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `createMaintenanceWindow`. Schedules a maintenance window of at most 30 days. While it runs, alerts are still recorded but nobody is paged, unless `silence_oncall` is `false`. Optionally it also fans out: `status_page_id` publishes a scheduled maintenance (with `affected_component_ids` and `message`) on that status page, and `uptime_monitor_ids` silences those uptime monitors for the window. Deleting the window undoes both. **Permission (API-key scope):** `oncall:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (oncall:maintenance:write) - `apiKeyHeader` (oncall:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `MaintenanceWindowCreate` - `title` (string, required) - `starts_at` (string (date-time), required) - `ends_at` (string (date-time), required): After `starts_at`, at most 30 days later. - `silence_oncall` (boolean, default `true`) - `status_page_id` (string): Also publish a scheduled maintenance on this status page. - `affected_component_ids` (array of string) - `message` (string): The window's description (`body`), also used as the status-page maintenance text. - `uptime_monitor_ids` (array of string) ## Responses ### 201 — The window. Content type: `application/json` Type: `object` - `data` (MaintenanceWindow, required) - `id` (string) - `org_id` (string) - `title` (string) - `body` (string): What the maintenance does. - `starts_at` (string (date-time)) - `ends_at` (string (date-time)) - `created_by` (string) - `created_at` (string (date-time)) - `ended_at` (string (date-time)): Set when the window was ended early. - `silence_oncall` (boolean): Whether paging is held back during the window. - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `title`, `starts_at` or `ends_at` is missing (`VALIDATION_FAILED`); or the window ends before it starts or is longer than 30 days (`INVALID_WINDOW`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/maintenance-windows' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "title": "Database upgrade", "ends_at": "2026-10-13T00:00:00Z", "message": "Expect a few minutes of read-only mode.", "starts_at": "2026-10-12T22:00:00Z", "status_page_id": "page_1f2e3d4c", "uptime_monitor_ids": [ "mon_7a8b9c" ], "affected_component_ids": [ "comp_api" ] }' ``` --- Source: https://docs.evohub.io/oncall/get-maintenance-window.md # Get a maintenance window `GET https://evohub.io/api/v1/maintenance-windows/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getMaintenanceWindow`. Returns one window. **Permission (API-key scope):** `oncall:maintenance:read`. ## Authorization Any one of: - `bearerAuth` (oncall:maintenance:read) - `apiKeyHeader` (oncall:maintenance:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f`): The maintenance window's id. ## Responses ### 200 — The window. Content type: `application/json` Type: `object` - `data` (MaintenanceWindow, required) - `id` (string) - `org_id` (string) - `title` (string) - `body` (string): What the maintenance does. - `starts_at` (string (date-time)) - `ends_at` (string (date-time)) - `created_by` (string) - `created_at` (string (date-time)) - `ended_at` (string (date-time)): Set when the window was ended early. - `silence_oncall` (boolean): Whether paging is held back during the window. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No maintenance window with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/maintenance-windows/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/delete-maintenance-window.md # Delete a maintenance window `DELETE https://evohub.io/api/v1/maintenance-windows/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `deleteMaintenanceWindow`. Deletes the window, removes the maintenance it published on a status page and clears the uptime silences it set. To end a window early but keep it as history, complete it instead. **Permission (API-key scope):** `oncall:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (oncall:maintenance:write) - `apiKeyHeader` (oncall:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f`): The maintenance window's id. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No maintenance window with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/maintenance-windows/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/update-maintenance-window.md # Update a maintenance window `PATCH https://evohub.io/api/v1/maintenance-windows/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `updateMaintenanceWindow`. Changes the title, description (`body`) or times, for example to extend a running window. A field left out is unchanged; a blank title is ignored. **Permission (API-key scope):** `oncall:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (oncall:maintenance:write) - `apiKeyHeader` (oncall:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f`): The maintenance window's id. ## Request body (required) Content type: `application/json` Type: `MaintenanceWindowUpdate` - `title` (string) - `body` (string) - `starts_at` (string (date-time)) - `ends_at` (string (date-time)) ## Responses ### 200 — The window after the change. Content type: `application/json` Type: `object` - `data` (MaintenanceWindow, required) - `id` (string) - `org_id` (string) - `title` (string) - `body` (string): What the maintenance does. - `starts_at` (string (date-time)) - `ends_at` (string (date-time)) - `created_by` (string) - `created_at` (string (date-time)) - `ended_at` (string (date-time)): Set when the window was ended early. - `silence_oncall` (boolean): Whether paging is held back during the window. - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or the window would end before it starts or be longer than 30 days (`INVALID_WINDOW`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No maintenance window with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/maintenance-windows/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "ends_at": "2026-10-13T01:00:00Z" }' ``` --- Source: https://docs.evohub.io/oncall/complete-maintenance-window.md # End a maintenance window `POST https://evohub.io/api/v1/maintenance-windows/{id}/complete` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `completeMaintenanceWindow`. Ends the window and keeps it as completed history: paging resumes, the uptime silences are cleared and the status-page maintenance is marked completed. Completing a completed window changes nothing. The body is optional. `ended_at` records when the work actually finished, for a window closed after the fact; it must be between the window's start and now (else 400 `INVALID_ENDED_AT`). Without it the window ends now. The status page dates its closing update at the same time. **Permission (API-key scope):** `oncall:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (oncall:maintenance:write) - `apiKeyHeader` (oncall:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f`): The maintenance window's id. ## Request body Content type: `application/json` Type: `object` - `ended_at` (string (date-time)): When the work finished. Between the window's start and now. ## Responses ### 200 — The window. Content type: `application/json` Type: `object` - `data` (MaintenanceWindow, required) - `id` (string) - `org_id` (string) - `title` (string) - `body` (string): What the maintenance does. - `starts_at` (string (date-time)) - `ends_at` (string (date-time)) - `created_by` (string) - `created_at` (string (date-time)) - `ended_at` (string (date-time)): Set when the window was ended early. - `silence_oncall` (boolean): Whether paging is held back during the window. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No maintenance window with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/maintenance-windows/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f/complete' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "ended_at": "2026-10-13T00:00:00Z" }' ``` --- Source: https://docs.evohub.io/oncall/list-maintenance-updates.md # List a window's updates `GET https://evohub.io/api/v1/maintenance-windows/{id}/updates` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listMaintenanceUpdates`. The progress notes posted on the window, oldest first. An unknown window id answers 404. **Permission (API-key scope):** `oncall:maintenance:read`. ## Authorization Any one of: - `bearerAuth` (oncall:maintenance:read) - `apiKeyHeader` (oncall:maintenance:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f`): The maintenance window's id. ## Responses ### 200 — The updates. Content type: `application/json` Type: `object` - `data` (array of MaintenanceUpdate, required) - `id` (string) - `window_id` (string) - `author_id` (string) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No maintenance window with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/maintenance-windows/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f/updates' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/add-maintenance-update.md # Post an update `POST https://evohub.io/api/v1/maintenance-windows/{id}/updates` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `addMaintenanceUpdate`. Posts a progress note on the window. When the window was published to a status page, the note is posted there too. **Permission (API-key scope):** `oncall:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (oncall:maintenance:write) - `apiKeyHeader` (oncall:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f`): The maintenance window's id. ## Request body (required) Content type: `application/json` Type: `UpdateBody` - `body` (string, required, min length 1) ## Responses ### 201 — The update. Content type: `application/json` Type: `object` - `data` (MaintenanceUpdate, required) - `id` (string) - `window_id` (string) - `author_id` (string) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`) or `body` is empty (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No maintenance window with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/maintenance-windows/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f/updates' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "Replica promoted; switching traffic." }' ``` --- Source: https://docs.evohub.io/oncall/delete-maintenance-update.md # Delete an update `DELETE https://evohub.io/api/v1/maintenance-windows/{id}/updates/{updateID}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `deleteMaintenanceUpdate`. Removes an update, from the status page too when it was posted there. **Permission (API-key scope):** `oncall:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (oncall:maintenance:write) - `apiKeyHeader` (oncall:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f`): The maintenance window's id. - `updateID` (string, required, example `d5e6f7a8-b9c0-4d1e-a2f3-b4c5d6e7f8a9`): The update's id. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such update on this window (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/maintenance-windows/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f/updates/d5e6f7a8-b9c0-4d1e-a2f3-b4c5d6e7f8a9' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/edit-maintenance-update.md # Edit an update `PATCH https://evohub.io/api/v1/maintenance-windows/{id}/updates/{updateID}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `editMaintenanceUpdate`. Replaces an update's text, on the status page too when it was posted there. **Permission (API-key scope):** `oncall:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (oncall:maintenance:write) - `apiKeyHeader` (oncall:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f`): The maintenance window's id. - `updateID` (string, required, example `d5e6f7a8-b9c0-4d1e-a2f3-b4c5d6e7f8a9`): The update's id. ## Request body (required) Content type: `application/json` Type: `UpdateBody` - `body` (string, required, min length 1) ## Responses ### 200 — The update after the change. Content type: `application/json` Type: `object` - `data` (MaintenanceUpdate, required) - `id` (string) - `window_id` (string) - `author_id` (string) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`) or `body` is empty (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such update on this window (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/maintenance-windows/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f/updates/d5e6f7a8-b9c0-4d1e-a2f3-b4c5d6e7f8a9' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "Replica promoted; traffic switched." }' ``` --- Source: https://docs.evohub.io/oncall/get-alert-analytics.md # Alert analytics `GET https://evohub.io/api/v1/alerts/analytics` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getAlertAnalytics`. Aggregates over the organization's alerts in a time range: MTTA and MTTR (p50, p90, average), counts per severity and per integration, a time series (hourly up to 48 hours, daily beyond), noise (flapping alerts, alerts resolved without anyone acknowledging them, the noisiest fingerprints), an alerts-per- hour heatmap, notification totals and the load on each responder. The heatmap and off-hours counts use `tz`, else the timezone most of the organization's schedules use, else UTC; `range.tz_source` says which. **Permission (API-key scope):** `oncall:alert:read`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:read) - `apiKeyHeader` (oncall:alert:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `from` (string, example `2026-09-01`): Start of the range: a date (`YYYY-MM-DD`, start of that UTC day) or an RFC 3339 instant. Give `from` and `to` together, or neither for the last 30 UTC days including today. - `to` (string, example `2026-09-30`): End of the range: a date (the last UTC day included) or an RFC 3339 instant (exclusive). At most 366 days after `from`. - `severity` (string, example `critical,high`): Comma-separated severities to include. - `compare` (string, one of `previous`, `year`): Also compute the totals of a comparison period: `previous` (the same length immediately before) or `year` (the same dates a year earlier). - `team_id` (string): A team id (that team's alerts plus organization-wide ones) or `none` (organization-wide alerts only), as on the alert list. - `integration_id` (string): Comma-separated integration ids. - `source` (string): Comma-separated integration kinds, for example `prometheus,grafana`. - `tz` (string, example `Europe/Istanbul`): IANA timezone for the heatmap and off-hours counts. - `ack_target_seconds` (integer, default `300`, min 1, max 86400): The acknowledgement target `acked_within_target` counts against, in seconds (1–86400). - `flap_window_minutes` (integer, default `30`, min 1, max 1440): An alert re-opening within this many minutes of being resolved counts as flapping (1–1440). ## Responses ### 200 — The aggregates. Content type: `application/json` Type: `object` - `data` (AlertAnalytics, required) - `range` (AnalyticsRange) - `from` (string (date-time)) - `to` (string (date-time)): Exclusive. - `granularity` (string, one of `hour`, `day`) - `tz` (string) - `tz_source` (string, one of `param`, `schedules`, `default`) - `ack_target_seconds` (integer) - `totals` (AlertGroupStats) - `alerts` (integer) - `acknowledged` (integer) - `resolved` (integer) - `auto_resolved` (integer) - `resolved_without_ack` (integer) - `escalated` (integer) - `retriggers` (integer) - `flapping` (integer) - `suppressed` (integer) - `closed_before_notify` (integer) - `acked_within_target` (integer) - `mtta` (DurationStats) - `mttr` (DurationStats) - `series` (array of AlertGroupStats & object) - `alerts` (integer) - `acknowledged` (integer) - `resolved` (integer) - `auto_resolved` (integer) - `resolved_without_ack` (integer) - `escalated` (integer) - `retriggers` (integer) - `flapping` (integer) - `suppressed` (integer) - `closed_before_notify` (integer) - `acked_within_target` (integer) - `mtta` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `mttr` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `bucket` (string (date-time)) - `by_severity` (object) - Other keys: integer - `by_severity` (array of AlertGroupStats & object) - `alerts` (integer) - `acknowledged` (integer) - `resolved` (integer) - `auto_resolved` (integer) - `resolved_without_ack` (integer) - `escalated` (integer) - `retriggers` (integer) - `flapping` (integer) - `suppressed` (integer) - `closed_before_notify` (integer) - `acked_within_target` (integer) - `mtta` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `mttr` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `severity` (string) - `by_integration` (array of AlertIntegrationStats) - `alerts` (integer) - `acknowledged` (integer) - `resolved` (integer) - `auto_resolved` (integer) - `resolved_without_ack` (integer) - `escalated` (integer) - `retriggers` (integer) - `flapping` (integer) - `suppressed` (integer) - `closed_before_notify` (integer) - `acked_within_target` (integer) - `mtta` (DurationStats) - `mttr` (DurationStats) - `integration_id` (string): Empty for alerts raised by hand or by another EvoHub product. - `integration_name` (string) - `integration_type` (string) - `noise` (object) - `flap_window_minutes` (integer) - `flapping` (integer) - `resolved_without_ack` (integer) - `top_integrations` (array of AlertIntegrationStats) - `alerts` (integer) - `acknowledged` (integer) - `resolved` (integer) - `auto_resolved` (integer) - `resolved_without_ack` (integer) - `escalated` (integer) - `retriggers` (integer) - `flapping` (integer) - `suppressed` (integer) - `closed_before_notify` (integer) - `acked_within_target` (integer) - `mtta` (DurationStats) - `mttr` (DurationStats) - `integration_id` (string): Empty for alerts raised by hand or by another EvoHub product. - `integration_name` (string) - `integration_type` (string) - `top_fingerprints` (array of object) - `fingerprint` (string) - `title` (string) - `integration_id` (string) - `alerts` (integer) - `resolved_without_ack` (integer) - `retriggers` (integer) - `flapping` (integer) - `heatmap` (array of object) - `weekday` (integer, min 1, max 7): ISO weekday: 1 Monday … 7 Sunday. - `hour` (integer, min 0, max 23) - `alerts` (integer) - `notifications` (object) - `total` (integer) - `voice` (integer) - `sms` (integer) - `email` (integer) - `push` (integer) - `calls_unanswered` (integer) - `suppressed` (integer) - `responders` (array of object) - `user_id` (string) - `notifications` (integer) - `voice` (integer) - `sms` (integer) - `email` (integer) - `push` (integer) - `calls_unanswered` (integer) - `alerts_notified` (integer) - `interruptions` (integer) - `off_hours_interruptions` (integer) - `sleep_hours_interruptions` (integer) - `acknowledged` (integer) - `resolved` (integer) - `suppressed` (integer): Notifications held back by maintenance or the person's notification schedule. - `compare` (AlertGroupStats & object | null) - One of: - AlertGroupStats & object - `alerts` (integer) - `acknowledged` (integer) - `resolved` (integer) - `auto_resolved` (integer) - `resolved_without_ack` (integer) - `escalated` (integer) - `retriggers` (integer) - `flapping` (integer) - `suppressed` (integer) - `closed_before_notify` (integer) - `acked_within_target` (integer) - `mtta` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `mttr` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `mode` (string) - `from` (string (date-time)) - `to` (string (date-time)) - null - `success` (boolean, required, value `true`) ### 400 — A parameter is invalid; each problem is listed in `details` (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/alerts/analytics' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/get-incident-analytics.md # Incident analytics `GET https://evohub.io/api/v1/incidents/analytics` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getIncidentAnalytics`. Aggregates over the organization's incidents in a time range: counts, MTTR, time to identify, time to first status-page publication, postmortem rates, a time series (hourly up to 48 hours, daily beyond), a breakdown per severity and the incidents themselves (`incidents_truncated` is true when the list was cut short). Computed in UTC. Incidents carry no team, integration or source, so `team_id`, `integration_id` and `source` are refused. **Permission (API-key scope):** `oncall:incident:read`. ## Authorization Any one of: - `bearerAuth` (oncall:incident:read) - `apiKeyHeader` (oncall:incident:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `from` (string, example `2026-09-01`): Start of the range: a date (`YYYY-MM-DD`, start of that UTC day) or an RFC 3339 instant. Give `from` and `to` together, or neither for the last 30 UTC days including today. - `to` (string, example `2026-09-30`): End of the range: a date (the last UTC day included) or an RFC 3339 instant (exclusive). At most 366 days after `from`. - `severity` (string, example `major,critical`): Comma-separated incident severities to include. - `compare` (string, one of `previous`, `year`): Also compute the totals of a comparison period: `previous` (the same length immediately before) or `year` (the same dates a year earlier). ## Responses ### 200 — The aggregates. Content type: `application/json` Type: `object` - `data` (IncidentAnalytics, required) - `range` (AnalyticsRange) - `from` (string (date-time)) - `to` (string (date-time)): Exclusive. - `granularity` (string, one of `hour`, `day`) - `tz` (string) - `tz_source` (string, one of `param`, `schedules`, `default`) - `totals` (IncidentGroupStats & object) - `opened` (integer) - `resolved` (integer) - `mttr` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `time_to_identify` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `published_to_status_page` (integer) - `time_to_first_publication` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `postmortem` (PostmortemRate & object) - `eligible` (integer) - `published` (integer) - `rate` (number | null) - `time_to_postmortem` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `high_severity_postmortem` (PostmortemRate) - `eligible` (integer) - `published` (integer) - `rate` (number | null) - `resolved_in_range` (integer): Incidents resolved in the range, whenever they started. - `series` (array of object) - `bucket` (string (date-time)) - `opened` (integer) - `resolved` (integer) - `by_severity` (array of IncidentGroupStats & object) - `opened` (integer) - `resolved` (integer) - `mttr` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `time_to_identify` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `published_to_status_page` (integer) - `time_to_first_publication` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `postmortem` (PostmortemRate & object) - `eligible` (integer) - `published` (integer) - `rate` (number | null) - `time_to_postmortem` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `high_severity_postmortem` (PostmortemRate) - `eligible` (integer) - `published` (integer) - `rate` (number | null) - `severity` (string) - `incidents` (array of object) - `id` (string) - `number` (integer) - `title` (string) - `severity` (string) - `status` (string) - `started_at` (string (date-time)) - `identified_at` (string (date-time) | null) - `resolved_at` (string (date-time) | null) - `alerts` (integer) - `first_alert_acknowledged_at` (string (date-time) | null) - `published_to_status_page_at` (string (date-time) | null) - `postmortem_status` (string) - `postmortem_published_at` (string (date-time) | null) - `incidents_truncated` (boolean) - `compare` (IncidentGroupStats & object | null) - One of: - IncidentGroupStats & object - `opened` (integer) - `resolved` (integer) - `mttr` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `time_to_identify` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `published_to_status_page` (integer) - `time_to_first_publication` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `postmortem` (PostmortemRate & object) - `eligible` (integer) - `published` (integer) - `rate` (number | null) - `time_to_postmortem` (DurationStats) - `count` (integer): How many were measured. - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `high_severity_postmortem` (PostmortemRate) - `eligible` (integer) - `published` (integer) - `rate` (number | null) - `mode` (string) - `from` (string (date-time)) - `to` (string (date-time)) - null - `success` (boolean, required, value `true`) ### 400 — A parameter is invalid or not supported here; each problem is listed in `details` (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/incidents/analytics' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-update-templates.md # List status-update templates `GET https://evohub.io/api/v1/update-templates` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listUpdateTemplates`. The organization's default texts for status updates, one per lifecycle status that has one: incident statuses `investigating`, `identified`, `monitoring`, `resolved`, and maintenance statuses `scheduled`, `in_progress`, `completed`. **Permission:** none — any valid API key or signed-in member of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The templates. Content type: `application/json` Type: `object` - `data` (array of UpdateTemplate, required) - `id` (string) - `org_id` (string) - `kind` (string, one of `incident`, `maintenance`) - `status` (string) - `body` (string) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/update-templates' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/save-update-template.md # Save a status-update template `PUT https://evohub.io/api/v1/update-templates` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `saveUpdateTemplate`. Sets the default text for one kind and status. An empty `body` is stored as empty. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `UpdateTemplateWrite` - `kind` (string, required, one of `incident`, `maintenance`) - `status` (string, required): For `incident`: investigating, identified, monitoring, resolved. For `maintenance`: scheduled, in_progress, completed. - `body` (string) ## Responses ### 200 — The template. Content type: `application/json` Type: `object` - `data` (UpdateTemplate, required) - `id` (string) - `org_id` (string) - `kind` (string, one of `incident`, `maintenance`) - `status` (string) - `body` (string) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or the kind and status do not go together (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/update-templates' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "We are investigating reports of degraded service.", "kind": "incident", "status": "investigating" }' ``` --- Source: https://docs.evohub.io/oncall/get-voice-template.md # Get the voice-call template `GET https://evohub.io/api/v1/voice-template` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getVoiceTemplate`. The organization's voice template: the text a phone call reads out about an alert, with `{field}` placeholders filled from the alert. An organization without one gets an empty `body` and uses the default call. **Permission:** none — any valid API key or signed-in member of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The template. Content type: `application/json` Type: `object` - `data` (VoiceTemplate, required) - `body` (string) - `updated_at` (string (date-time)): RFC 3339; absent when no template is saved. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/voice-template' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/save-voice-template.md # Save the voice-call template `PUT https://evohub.io/api/v1/voice-template` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `saveVoiceTemplate`. Saves the voice template (at most 1000 characters; surrounding spaces are trimmed). An empty `body` removes it and calls go back to the default wording. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `VoiceTemplateWrite` - `body` (string, max length 1000): Placeholders in braces, e.g. `{alert.title}` or `{service}`. ## Responses ### 200 — The saved template, or an empty one after removal. Content type: `application/json` Type: `object` - `data` (VoiceTemplate, required) - `body` (string) - `updated_at` (string (date-time)): RFC 3339; absent when no template is saved. - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or the template is longer than 1000 characters (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/voice-template' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "Service {service} in {env}: {alert.title}." }' ``` --- Source: https://docs.evohub.io/oncall/preview-voice-template.md # Preview a voice-call template `POST https://evohub.io/api/v1/voice-template/preview` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `previewVoiceTemplate`. Renders a template without saving it: `spoken` is the whole call script (its fixed opening and key instructions are in Turkish), `rendered` the template's own part (cut at 200 characters, `truncated`). The fields are filled from sample values the organization's alerts have carried. It also reports names no alert has ever sent (`unknown_fields`, likely typos), which integrations send each name, the integrations whose alerts carry every name (`complete_on`), and names that were empty in this sample. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `VoiceTemplateWrite` - `body` (string, max length 1000): Placeholders in braces, e.g. `{alert.title}` or `{service}`. ## Responses ### 200 — The preview. Content type: `application/json` Type: `object` - `data` (VoicePreview, required) - `spoken` (string) - `rendered` (string) - `truncated` (boolean) - `alert_title` (string) - `unknown_fields` (array of string) - `field_sources` (object) - Other keys: array of string - `complete_on` (array of string) - `blank_fields` (array of string) - `success` (boolean, required, value `true`) ### 400 — The body is not valid JSON (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/voice-template/preview' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "Service {service} in {env}: {alert.title}." }' ``` --- Source: https://docs.evohub.io/oncall/list-notification-preferences.md # List my notification methods `GET https://evohub.io/api/v1/notification-preferences` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listNotificationPreferences`. The ways the caller is notified when paged — push, voice call, email (SMS is coming soon) — and their targets. These are the caller's own, across every organization they belong to. Meant for a person; with an organization API key the key itself is the caller. **Permission:** none — any valid API key or signed-in member of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The preferences. Content type: `application/json` Type: `object` - `data` (array of NotificationPreference, required) - `id` (string) - `org_id` (string): The organization it was created from. - `user_id` (string) - `channel` (NotificationChannel, one of `push`, `voice`, `email`, `sms`) - `target` (string): Phone number, email address, or empty for push. - `priority` (integer): 0 is tried first. - `enabled` (boolean) - `quiet_start` (string): Stored for older clients; no longer used. Use the notification schedule. - `quiet_end` (string): Stored for older clients; no longer used. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/notification-preferences' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/create-notification-preference.md # Add a notification method `POST https://evohub.io/api/v1/notification-preferences` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `createNotificationPreference`. Adds a way to notify the caller. `target` is a phone number in international form (`+` and the country code) for `voice` and `sms`, an address for `email`, and not needed for `push` (the caller's signed-in devices are used). SMS is coming soon and is not offered yet; an `sms` method may be refused. `enabled` defaults to true. These are the caller's own, across every organization they belong to. Meant for a person; with an organization API key the key itself is the caller. **Permission:** none — any valid API key or signed-in member of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `NotificationPreferenceCreate` - `channel` (NotificationChannel, required, one of `push`, `voice`, `email`, `sms`) - `target` (string) - `priority` (integer) - `enabled` (boolean, default `true`) - `quiet_start` (string) - `quiet_end` (string) ## Responses ### 201 — The preference. Content type: `application/json` Type: `object` - `data` (NotificationPreference, required) - `id` (string) - `org_id` (string): The organization it was created from. - `user_id` (string) - `channel` (NotificationChannel, one of `push`, `voice`, `email`, `sms`) - `target` (string): Phone number, email address, or empty for push. - `priority` (integer): 0 is tried first. - `enabled` (boolean) - `quiet_start` (string): Stored for older clients; no longer used. Use the notification schedule. - `quiet_end` (string): Stored for older clients; no longer used. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or it was refused (`VALIDATION_ERROR`, the message says why): unknown channel, missing or malformed target, SMS unavailable, or the same channel and target already exist. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/notification-preferences' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "target": "+905321234567", "channel": "voice", "priority": 0 }' ``` --- Source: https://docs.evohub.io/oncall/update-notification-preference.md # Update a notification method `PUT https://evohub.io/api/v1/notification-preferences/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `updateNotificationPreference`. Changes one of the caller's notification methods. Only the fields sent change; PUT and PATCH do the same thing. These are the caller's own, across every organization they belong to. Meant for a person; with an organization API key the key itself is the caller. **Permission:** none — any valid API key or signed-in member of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The notification preference's id. ## Request body (required) Content type: `application/json` Type: `NotificationPreferenceUpdate` Only the fields sent change. - `channel` (NotificationChannel, one of `push`, `voice`, `email`, `sms`) - `target` (string) - `priority` (integer) - `enabled` (boolean) - `quiet_start` (string) - `quiet_end` (string) ## Responses ### 200 — The preference after the change. Content type: `application/json` Type: `object` - `data` (NotificationPreference, required) - `id` (string) - `org_id` (string): The organization it was created from. - `user_id` (string) - `channel` (NotificationChannel, one of `push`, `voice`, `email`, `sms`) - `target` (string): Phone number, email address, or empty for push. - `priority` (integer): 0 is tried first. - `enabled` (boolean) - `quiet_start` (string): Stored for older clients; no longer used. Use the notification schedule. - `quiet_end` (string): Stored for older clients; no longer used. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or a value is refused (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — The caller has no preference with this id (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/notification-preferences/string' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "enabled": false }' ``` --- Source: https://docs.evohub.io/oncall/delete-notification-preference.md # Remove a notification method `DELETE https://evohub.io/api/v1/notification-preferences/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `deleteNotificationPreference`. Removes one of the caller's notification methods. These are the caller's own, across every organization they belong to. Meant for a person; with an organization API key the key itself is the caller. **Permission:** none — any valid API key or signed-in member of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The notification preference's id. ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — The caller has no preference with this id (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/notification-preferences/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/update-notification-preference-partial.md # Update a notification method (PATCH) `PATCH https://evohub.io/api/v1/notification-preferences/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `updateNotificationPreferencePartial`. Changes one of the caller's notification methods. Only the fields sent change; PUT and PATCH do the same thing. These are the caller's own, across every organization they belong to. Meant for a person; with an organization API key the key itself is the caller. **Permission:** none — any valid API key or signed-in member of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The notification preference's id. ## Request body (required) Content type: `application/json` Type: `NotificationPreferenceUpdate` Only the fields sent change. - `channel` (NotificationChannel, one of `push`, `voice`, `email`, `sms`) - `target` (string) - `priority` (integer) - `enabled` (boolean) - `quiet_start` (string) - `quiet_end` (string) ## Responses ### 200 — The preference after the change. Content type: `application/json` Type: `object` - `data` (NotificationPreference, required) - `id` (string) - `org_id` (string): The organization it was created from. - `user_id` (string) - `channel` (NotificationChannel, one of `push`, `voice`, `email`, `sms`) - `target` (string): Phone number, email address, or empty for push. - `priority` (integer): 0 is tried first. - `enabled` (boolean) - `quiet_start` (string): Stored for older clients; no longer used. Use the notification schedule. - `quiet_end` (string): Stored for older clients; no longer used. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or a value is refused (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — The caller has no preference with this id (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/notification-preferences/string' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "enabled": false }' ``` --- Source: https://docs.evohub.io/oncall/get-notification-schedule.md # Get my notification schedule `GET https://evohub.io/api/v1/notification-schedule` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getNotificationSchedule`. The caller's notification schedule: rules that limit which methods may reach them at certain times (for example only push at night). Someone who never saved one gets `UTC`, no rules and `updated_at: null`. These are the caller's own, across every organization they belong to. Meant for a person; with an organization API key the key itself is the caller. **Permission:** none — any valid API key or signed-in member of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The schedule. Content type: `application/json` Type: `object` - `data` (NotificationSchedule, required) - `timezone` (string) - `rules` (array of NotificationRule) - `days` (array of string, required, min items 1) - `start` (string, required): `HH:MM` - `end` (string, required): `HH:MM` - `channels` (array of NotificationChannel, required) - `critical_overrides` (boolean): Critical alerts ignore this rule. - `updated_at` (string (date-time) | null) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — No caller to attach the schedule to (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/notification-schedule' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/save-notification-schedule.md # Save my notification schedule `PUT https://evohub.io/api/v1/notification-schedule` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `saveNotificationSchedule`. Replaces the caller's timezone and rules. At most 20 rules. Each rule names days (`mon`…`sun`), a `start` and `end` (`HH:MM`, may cross midnight), and the `channels` (`push`, `voice`, `email`, `sms`) allowed during it; an empty list allows none. With `critical_overrides`, critical alerts ignore the rule. The first matching rule applies. An empty `rules` list turns the schedule off. These are the caller's own, across every organization they belong to. Meant for a person; with an organization API key the key itself is the caller. **Permission:** none — any valid API key or signed-in member of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `NotificationScheduleWrite` - `timezone` (string, required): IANA timezone. - `rules` (array of NotificationRule, max items 20) - `days` (array of string, required, min items 1) - `start` (string, required): `HH:MM` - `end` (string, required): `HH:MM` - `channels` (array of NotificationChannel, required) - `critical_overrides` (boolean): Critical alerts ignore this rule. ## Responses ### 200 — The saved schedule. Content type: `application/json` Type: `object` - `data` (NotificationSchedule, required) - `timezone` (string) - `rules` (array of NotificationRule) - `days` (array of string, required, min items 1) - `start` (string, required): `HH:MM` - `end` (string, required): `HH:MM` - `channels` (array of NotificationChannel, required) - `critical_overrides` (boolean): Critical alerts ignore this rule. - `updated_at` (string (date-time) | null) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON or larger than 64 KB (`INVALID_BODY`), or it is invalid (`VALIDATION_ERROR`, the message says which rule and why). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — No caller to attach the schedule to (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/notification-schedule' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "rules": [ { "end": "08:00", "days": [ "mon", "tue", "wed", "thu", "fri" ], "start": "22:00", "channels": [ "push" ], "critical_overrides": true } ], "timezone": "Europe/Istanbul" }' ``` --- Source: https://docs.evohub.io/oncall/get-slack-connection.md # Get the Slack connection `GET https://evohub.io/api/v1/slack` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getSlackConnection`. Whether Slack is available on this EvoHub and connected for the organization, the workspace name and the default channel alerts are posted to. **Permission (API-key scope):** `oncall:escalation:read`. ## Authorization Any one of: - `bearerAuth` (oncall:escalation:read) - `apiKeyHeader` (oncall:escalation:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The connection. Content type: `application/json` Type: `object` - `data` (SlackStatus, required) - `available` (boolean): Slack is set up on this EvoHub. - `connected` (boolean) - `team_name` (string) - `default_channel_id` (string) - `default_channel_name` (string) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/slack' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/disconnect-slack.md # Disconnect Slack `DELETE https://evohub.io/api/v1/slack` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `disconnectSlack`. Disconnects the workspace and revokes EvoHub's Slack token. Succeeds when nothing was connected. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 204 — Disconnected. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/slack' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/start-slack-install.md # Start connecting Slack `POST https://evohub.io/api/v1/slack/install` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `startSlackInstall`. Returns the Slack authorization URL to open in a browser. After the workspace admin approves, Slack sends the browser back to EvoHub, which finishes the connection. No request body. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The URL to open. Content type: `application/json` Type: `object` - `data` (object, required) - `url` (string (uri)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — Slack is not available on this EvoHub (`SLACK_NOT_AVAILABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/slack/install' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-slack-channels.md # List Slack channels `GET https://evohub.io/api/v1/slack/channels` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listSlackChannels`. The connected workspace's channels, for choosing where an escalation step or the default channel posts. **Permission (API-key scope):** `oncall:escalation:write`. ## Authorization Any one of: - `bearerAuth` (oncall:escalation:write) - `apiKeyHeader` (oncall:escalation:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The channels. Content type: `application/json` Type: `object` - `data` (array of SlackChannel, required) - `id` (string) - `name` (string) - `private` (boolean) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — Slack is not connected (`SLACK_NOT_CONNECTED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 502 — Slack did not return the list (`SLACK_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/slack/channels' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/set-slack-default-channel.md # Set the default Slack channel `PUT https://evohub.io/api/v1/slack/default-channel` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `setSlackDefaultChannel`. Sets the channel every alert is posted to. `channel_id` is Slack's channel id (capital letters and digits); `channel_name` is kept only to display it and a leading `#` is dropped. An empty `channel_id` clears the default. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `channel_id` (string, max length 32) - `channel_name` (string, max length 200) ## Responses ### 200 — The default channel. Content type: `application/json` Type: `object` - `data` (object, required) - `default_channel_id` (string) - `default_channel_name` (string) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_REQUEST`), or the channel id or name is malformed (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — Slack is not connected (`SLACK_NOT_CONNECTED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/slack/default-channel' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "channel_id": "C0123ABCD", "channel_name": "#alerts" }' ``` --- Source: https://docs.evohub.io/oncall/get-chat-status.md # Get chat availability `GET https://evohub.io/api/v1/chat` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `getChatStatus`. Whether Microsoft Teams channels are available. **Permission (API-key scope):** `oncall:escalation:read`. ## Authorization Any one of: - `bearerAuth` (oncall:escalation:read) - `apiKeyHeader` (oncall:escalation:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — Availability. Content type: `application/json` Type: `object` - `data` (object, required) - `teams_available` (boolean) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/chat' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/list-chat-channels.md # List Teams channels `GET https://evohub.io/api/v1/chat/channels` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listChatChannels`. The organization's Microsoft Teams channels. The webhook URL behind each one is stored encrypted and never returned. **Permission (API-key scope):** `oncall:escalation:read`. ## Authorization Any one of: - `bearerAuth` (oncall:escalation:read) - `apiKeyHeader` (oncall:escalation:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The channels. Content type: `application/json` Type: `object` - `data` (array of ChatChannel, required) - `id` (string) - `org_id` (string) - `kind` (string, value `teams`) - `name` (string) - `all_alerts` (boolean): Every alert is posted here. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/chat/channels' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/create-chat-channel.md # Add a Teams channel `POST https://evohub.io/api/v1/chat/channels` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `createChatChannel`. Adds a Microsoft Teams channel by its Workflows webhook URL (https, on `powerplatform.com` or `logic.azure.com`). With `all_alerts`, every alert is posted to it; otherwise only escalation steps that name it post there. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `ChatChannelCreate` - `kind` (string, required, value `teams`) - `name` (string, required, max length 200) - `webhook_url` (string (uri), required): A Teams Workflows webhook URL. - `all_alerts` (boolean) ## Responses ### 201 — The channel. Content type: `application/json` Type: `object` - `data` (ChatChannel, required) - `id` (string) - `org_id` (string) - `kind` (string, value `teams`) - `name` (string) - `all_alerts` (boolean): Every alert is posted here. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_REQUEST`); or `kind` is not `teams`, `name` is empty or over 200 characters, or the webhook URL is not a Teams Workflows URL (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/chat/channels' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "kind": "teams", "name": "Ops alerts", "all_alerts": true, "webhook_url": "https://prod-00.westeurope.logic.azure.com/workflows/0123456789abcdef/triggers/manual/paths/invoke" }' ``` --- Source: https://docs.evohub.io/oncall/delete-chat-channel.md # Remove a Teams channel `DELETE https://evohub.io/api/v1/chat/channels/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `deleteChatChannel`. Removes the channel. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `e4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8`): The chat channel's id. ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such channel (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/chat/channels/e4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/update-chat-channel.md # Update a Teams channel `PATCH https://evohub.io/api/v1/chat/channels/{id}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `updateChatChannel`. Changes the name, `all_alerts` or the webhook URL. Only the fields sent change. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `e4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8`): The chat channel's id. ## Request body (required) Content type: `application/json` Type: `ChatChannelUpdate` - `name` (string, max length 200) - `all_alerts` (boolean) - `webhook_url` (string (uri)) ## Responses ### 200 — The channel after the change. Content type: `application/json` Type: `object` - `data` (ChatChannel, required) - `id` (string) - `org_id` (string) - `kind` (string, value `teams`) - `name` (string) - `all_alerts` (boolean): Every alert is posted here. - `created_by` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_REQUEST`), or a field is invalid (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The request's `X-Request-ID`. Quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such channel (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/chat/channels/e4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "all_alerts": false }' ``` --- Source: https://docs.evohub.io/oncall/test-chat-channel.md # Send a test message `POST https://evohub.io/api/v1/chat/channels/{id}/test` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `testChatChannel`. Posts a test message to the channel. No request body. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `e4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8`): The chat channel's id. ## Responses ### 200 — Teams accepted the message. Content type: `application/json` Type: `object` - `data` (object, required) - `sent` (boolean, value `true`) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No such channel (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 502 — Teams did not accept the message (`CHAT_SEND_FAILED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/chat/channels/e4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8/test' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/preview-chat-action.md # Preview a Teams action link `GET https://evohub.io/api/v1/chat/actions/{token}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `previewChatAction`. The buttons on an alert card posted to Teams are signed links. This reads one: which action it would take (`ack`, `resolve` or `takeover`), on which alert, and until when the link works. It changes nothing. **Permission (API-key scope):** `oncall:alert:read`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:read) - `apiKeyHeader` (oncall:alert:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `token` (string, required): The signed token from the action link on a Microsoft Teams card. ## Responses ### 200 — What the link would do. Content type: `application/json` Type: `object` - `data` (ChatActionPreview, required) - `action` (string, one of `ack`, `resolve`, `takeover`) - `alert` (ActionAlert) - `id` (string) - `title` (string) - `status` (AlertStatus, one of `triggered`, `acknowledged`, `resolved`, `suppressed`) - `severity` (AlertSeverity, one of `critical`, `high`, `medium`, `low`, `info`) - `expires_at` (string (date-time)) - `via` (string) - `success` (boolean, required, value `true`) ### 400 — The link is invalid or has expired (`INVALID_ACTION_LINK`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The caller lacks the permission (`FORBIDDEN`), the link belongs to another organization (`WRONG_ORG`), or the alert belongs to a team the caller is not in and is not assigned to them (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No alert with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/chat/actions/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/run-chat-action.md # Run a Teams action link `POST https://evohub.io/api/v1/chat/actions/{token}` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `runChatAction`. Takes the link's action on the alert as the caller and notes it on the alert's timeline. Each link works once. `result` is `done`, or `already` when the alert was already in that state. No request body. **Permission (API-key scope):** `oncall:alert:respond`. ## Authorization Any one of: - `bearerAuth` (oncall:alert:respond) - `apiKeyHeader` (oncall:alert:respond) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `token` (string, required): The signed token from the action link on a Microsoft Teams card. ## Responses ### 200 — The outcome. Content type: `application/json` Type: `object` - `data` (ChatActionResult, required) - `action` (string, one of `ack`, `resolve`, `takeover`) - `result` (string, one of `done`, `already`) - `alert` (ActionAlert) - `id` (string) - `title` (string) - `status` (AlertStatus, one of `triggered`, `acknowledged`, `resolved`, `suppressed`) - `severity` (AlertSeverity, one of `critical`, `high`, `medium`, `low`, `info`) - `success` (boolean, required, value `true`) ### 400 — The link is invalid or has expired (`INVALID_ACTION_LINK`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The caller lacks the permission (`FORBIDDEN`), the link belongs to another organization (`WRONG_ORG`), or the alert belongs to a team the caller is not in and is not assigned to them (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 404 — No alert with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — The link was already used (`ACTION_LINK_USED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/chat/actions/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/oncall/preview-opsgenie-import.md # Preview an import from Opsgenie `POST https://evohub.io/api/v1/import/opsgenie/preview` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `previewOpsgenieImport`. Reads the Opsgenie account with `api_key` (a read-only key is enough; it is used for this request only and not stored) in `region` (`us` or `eu`), matches its users to EvoHub members by email, and works out what would be created. `team_map` maps Opsgenie team ids to EvoHub teams the caller is in; unmapped items go to the caller's first team. One import runs per organization at a time, within 25 seconds. Nothing is created. Items imported before are marked `already_imported`. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `ImportRequest` Preview reads only `api_key`, `region` and `team_map`. Apply also takes the source ids to import. - `api_key` (string, required, max length 200): The source account's API key; read access is enough. Not stored. - `region` (string, one of `us`, `eu`): Required for Opsgenie; for PagerDuty it defaults to `us`. - `schedules` (array of string) - `policies` (array of string) - `integrations` (array of string) - `team_map` (object): Source team id → EvoHub team id. - Other keys: string ## Responses ### 200 — What the account holds and what importing it would do. Content type: `application/json` Type: `object` - `data` (ImportPreview, required) - `source` (string, one of `opsgenie`, `pagerduty`) - `users` (array of object) - `source_id` (string) - `opsgenie_id` (string) - `email` (string) - `name` (string) - `evohub_user_id` (string | null): The matching EvoHub member, or null. - `teams` (array of object) - `source_id` (string) - `name` (string) - `schedules` (array of object) - `source_id` (string) - `opsgenie_id` (string) - `name` (string) - `timezone` (string) - `team` (string) - `team_source_id` (string) - `layers` (array of object) - `name` (string) - `summary` (string) - `participants` (array of string) - `already_imported` (boolean) - `new_members` (integer): For a schedule imported before: people who would be added now. - `warnings` (array of string) - `policies` (array of object) - `source_id` (string) - `opsgenie_id` (string) - `name` (string) - `team` (string) - `team_source_id` (string) - `steps` (array of object) - `after_minutes` (integer) - `targets` (array of string) - `already_imported` (boolean) - `warnings` (array of string) - `integrations` (array of object) - `source_id` (string) - `opsgenie_id` (string) - `name` (string) - `source_type` (string) - `opsgenie_type` (string) - `evohub_type` (string) - `policy` (string) - `team_source_id` (string) - `already_imported` (boolean) - `warnings` (array of string) - `not_imported` (array of string) - `warnings` (array of string) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `api_key`, `region` or `team_map` is invalid (`VALIDATION_FAILED`); Opsgenie refused the key (`OPSGENIE_UNAUTHORIZED`); or Opsgenie rejected the request (`OPSGENIE_REJECTED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The caller lacks the permission (`FORBIDDEN`), or `team_map` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — Another import is running for the organization (`IMPORT_IN_PROGRESS`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 502 — Opsgenie could not be read (`OPSGENIE_UNAVAILABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Opsgenie is rate limiting the key (`OPSGENIE_RATE_LIMITED`), or EvoHub members could not be checked (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/import/opsgenie/preview' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "region": "eu", "api_key": "YOUR_OPSGENIE_API_KEY" }' ``` --- Source: https://docs.evohub.io/oncall/apply-opsgenie-import.md # Import from Opsgenie `POST https://evohub.io/api/v1/import/opsgenie/apply` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `applyOpsgenieImport`. Reads the Opsgenie account with `api_key` (a read-only key is enough; it is used for this request only and not stored) in `region` (`us` or `eu`), matches its users to EvoHub members by email, and works out what would be created. `team_map` maps Opsgenie team ids to EvoHub teams the caller is in; unmapped items go to the caller's first team. One import runs per organization at a time, within 25 seconds. Creates the schedules, escalation policies and integrations whose source ids are listed in `schedules`, `policies` and `integrations`, in that order. Importing an item again does not duplicate it; a schedule imported before only gains the members who have joined EvoHub since. The answer lists what was created, updated and skipped, with each new integration's webhook URL. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `ImportRequest` Preview reads only `api_key`, `region` and `team_map`. Apply also takes the source ids to import. - `api_key` (string, required, max length 200): The source account's API key; read access is enough. Not stored. - `region` (string, one of `us`, `eu`): Required for Opsgenie; for PagerDuty it defaults to `us`. - `schedules` (array of string) - `policies` (array of string) - `integrations` (array of string) - `team_map` (object): Source team id → EvoHub team id. - Other keys: string ## Responses ### 200 — What was done. Content type: `application/json` Type: `object` - `data` (ImportResult, required) - `created` (object) - `schedules` (array of ImportedItem) - `id` (string): The new EvoHub id. - `name` (string) - `source_id` (string) - `opsgenie_id` (string) - `policies` (array of ImportedItem) - `id` (string): The new EvoHub id. - `name` (string) - `source_id` (string) - `opsgenie_id` (string) - `integrations` (array of object) - `id` (string) - `name` (string) - `source_id` (string) - `opsgenie_id` (string) - `evohub_type` (string) - `webhook_url` (string): Where to point the source tool. - `updated` (object) - `schedules` (array of object) - `id` (string) - `name` (string) - `source_id` (string) - `opsgenie_id` (string) - `added_members` (array of string) - `skipped` (array of object) - `kind` (string) - `name` (string) - `reason` (string) - `warnings` (array of string) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `api_key`, `region` or `team_map` is invalid (`VALIDATION_FAILED`); Opsgenie refused the key (`OPSGENIE_UNAUTHORIZED`); or Opsgenie rejected the request (`OPSGENIE_REJECTED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The caller lacks the permission (`FORBIDDEN`), or `team_map` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — Another import is running for the organization (`IMPORT_IN_PROGRESS`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 502 — Opsgenie could not be read (`OPSGENIE_UNAVAILABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — Opsgenie is rate limiting the key (`OPSGENIE_RATE_LIMITED`), or EvoHub members could not be checked (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/import/opsgenie/apply' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "region": "eu", "api_key": "YOUR_OPSGENIE_API_KEY", "policies": [ "P4D5E6F" ], "team_map": { "PTEAM01": "a3f9c2e1-7b4d-4c8a-9e6f-2d1b0c9a8f7e" }, "schedules": [ "P1A2B3C" ], "integrations": [ "P7G8H9I" ] }' ``` --- Source: https://docs.evohub.io/oncall/preview-pager-duty-import.md # Preview an import from PagerDuty `POST https://evohub.io/api/v1/import/pagerduty/preview` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `previewPagerDutyImport`. Reads the PagerDuty account with `api_key` (a REST API key; read-only is enough; it is used for this request only and not stored) in `region` (`us` or `eu`; default `us`), matches its users to EvoHub members by email, and works out what would be created. `team_map` maps PagerDuty team ids to EvoHub teams the caller is in; unmapped items go to the caller's first team. One import runs per organization at a time, within 25 seconds. Nothing is created. Items imported before are marked `already_imported`. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `ImportRequest` Preview reads only `api_key`, `region` and `team_map`. Apply also takes the source ids to import. - `api_key` (string, required, max length 200): The source account's API key; read access is enough. Not stored. - `region` (string, one of `us`, `eu`): Required for Opsgenie; for PagerDuty it defaults to `us`. - `schedules` (array of string) - `policies` (array of string) - `integrations` (array of string) - `team_map` (object): Source team id → EvoHub team id. - Other keys: string ## Responses ### 200 — What the account holds and what importing it would do. Content type: `application/json` Type: `object` - `data` (ImportPreview, required) - `source` (string, one of `opsgenie`, `pagerduty`) - `users` (array of object) - `source_id` (string) - `opsgenie_id` (string) - `email` (string) - `name` (string) - `evohub_user_id` (string | null): The matching EvoHub member, or null. - `teams` (array of object) - `source_id` (string) - `name` (string) - `schedules` (array of object) - `source_id` (string) - `opsgenie_id` (string) - `name` (string) - `timezone` (string) - `team` (string) - `team_source_id` (string) - `layers` (array of object) - `name` (string) - `summary` (string) - `participants` (array of string) - `already_imported` (boolean) - `new_members` (integer): For a schedule imported before: people who would be added now. - `warnings` (array of string) - `policies` (array of object) - `source_id` (string) - `opsgenie_id` (string) - `name` (string) - `team` (string) - `team_source_id` (string) - `steps` (array of object) - `after_minutes` (integer) - `targets` (array of string) - `already_imported` (boolean) - `warnings` (array of string) - `integrations` (array of object) - `source_id` (string) - `opsgenie_id` (string) - `name` (string) - `source_type` (string) - `opsgenie_type` (string) - `evohub_type` (string) - `policy` (string) - `team_source_id` (string) - `already_imported` (boolean) - `warnings` (array of string) - `not_imported` (array of string) - `warnings` (array of string) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `api_key`, `region` or `team_map` is invalid (`VALIDATION_FAILED`); or PagerDuty refused the key (`PAGERDUTY_UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The caller lacks the permission (`FORBIDDEN`), or `team_map` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — Another import is running for the organization (`IMPORT_IN_PROGRESS`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 502 — PagerDuty could not be read (`PAGERDUTY_UNAVAILABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — PagerDuty is rate limiting the key (`PAGERDUTY_RATE_LIMITED`), or EvoHub members could not be checked (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/import/pagerduty/preview' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "region": "eu", "api_key": "YOUR_PAGERDUTY_API_KEY" }' ``` --- Source: https://docs.evohub.io/oncall/apply-pager-duty-import.md # Import from PagerDuty `POST https://evohub.io/api/v1/import/pagerduty/apply` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `applyPagerDutyImport`. Reads the PagerDuty account with `api_key` (a REST API key; read-only is enough; it is used for this request only and not stored) in `region` (`us` or `eu`; default `us`), matches its users to EvoHub members by email, and works out what would be created. `team_map` maps PagerDuty team ids to EvoHub teams the caller is in; unmapped items go to the caller's first team. One import runs per organization at a time, within 25 seconds. Creates the schedules, escalation policies and integrations whose source ids are listed in `schedules`, `policies` and `integrations`, in that order. Importing an item again does not duplicate it; a schedule imported before only gains the members who have joined EvoHub since. The answer lists what was created, updated and skipped, with each new integration's webhook URL. **Permission (API-key scope):** `oncall:settings:write`. ## Authorization Any one of: - `bearerAuth` (oncall:settings:write) - `apiKeyHeader` (oncall:settings:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `ImportRequest` Preview reads only `api_key`, `region` and `team_map`. Apply also takes the source ids to import. - `api_key` (string, required, max length 200): The source account's API key; read access is enough. Not stored. - `region` (string, one of `us`, `eu`): Required for Opsgenie; for PagerDuty it defaults to `us`. - `schedules` (array of string) - `policies` (array of string) - `integrations` (array of string) - `team_map` (object): Source team id → EvoHub team id. - Other keys: string ## Responses ### 200 — What was done. Content type: `application/json` Type: `object` - `data` (ImportResult, required) - `created` (object) - `schedules` (array of ImportedItem) - `id` (string): The new EvoHub id. - `name` (string) - `source_id` (string) - `opsgenie_id` (string) - `policies` (array of ImportedItem) - `id` (string): The new EvoHub id. - `name` (string) - `source_id` (string) - `opsgenie_id` (string) - `integrations` (array of object) - `id` (string) - `name` (string) - `source_id` (string) - `opsgenie_id` (string) - `evohub_type` (string) - `webhook_url` (string): Where to point the source tool. - `updated` (object) - `schedules` (array of object) - `id` (string) - `name` (string) - `source_id` (string) - `opsgenie_id` (string) - `added_members` (array of string) - `skipped` (array of object) - `kind` (string) - `name` (string) - `reason` (string) - `warnings` (array of string) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`); `api_key`, `region` or `team_map` is invalid (`VALIDATION_FAILED`); or PagerDuty refused the key (`PAGERDUTY_UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The caller lacks the permission (`FORBIDDEN`), or `team_map` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 409 — Another import is running for the organization (`IMPORT_IN_PROGRESS`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 502 — PagerDuty could not be read (`PAGERDUTY_UNAVAILABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 503 — PagerDuty is rate limiting the key (`PAGERDUTY_RATE_LIMITED`), or EvoHub members could not be checked (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/import/pagerduty/apply' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "region": "eu", "api_key": "YOUR_PAGERDUTY_API_KEY", "policies": [ "P4D5E6F" ], "team_map": { "PTEAM01": "a3f9c2e1-7b4d-4c8a-9e6f-2d1b0c9a8f7e" }, "schedules": [ "P1A2B3C" ], "integrations": [ "P7G8H9I" ] }' ``` --- Source: https://docs.evohub.io/oncall/list-oncall-audit-logs.md # List the On-Call audit log `GET https://evohub.io/api/v1/oncall-audit-logs` Part of the [On-Call API](https://docs.evohub.io/oncall.md) reference · operationId `listOncallAuditLogs`. Who changed what in On-Call, newest first: alerts acknowledged, schedules edited, policies changed and so on. With both `resource_type` and `resource_id`, only that resource's entries. **Permission (API-key scope):** `oncall:audit:read`. ## Authorization Any one of: - `bearerAuth` (oncall:audit:read) - `apiKeyHeader` (oncall:audit:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `resource_type` (string, example `alert`): For example `alert`, `incident`, `schedule`, `escalation_policy`, `maintenance_window`. - `resource_id` (string): The resource's id; used only together with `resource_type`. - `limit` (integer, default `100`, min 1): How many entries (default 100, at most 500; a larger number returns 500). A value that is not a whole number is refused with 400 `VALIDATION_FAILED`. ## Responses ### 200 — The entries. Content type: `application/json` Type: `object` - `data` (array of AuditLog, required) - `id` (string) - `org_id` (string) - `user_id` (string): Who did it: a user id, or an API key's id for an organization key. - `resource_type` (string) - `resource_id` (string) - `action` (string): e.g. `alert.acknowledged`, `schedule.updated`. - `actor_type` (string): Currently always `user`. - `details` (object) - Other keys: any - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The request's `X-Request-ID`. Quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/oncall-audit-logs' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization.md # Organization API Version 1.0 · OpenAPI 3.1.0 The Organization API: the people in your organization, its teams, custom roles and who holds them, invitations, organization settings and its audit log. Use it to keep EvoHub in step with your directory or HR system. **Authentication.** Send an API key as `Authorization: Bearer evohub_…` or `X-API-Key: evohub_…`. A key works in one organization. See [API keys and scopes](/api-keys-and-scopes) and [Errors](/errors). **Permissions.** Each operation names the permission it needs (`x-evohub-permission`); an API key needs it among its scopes. A key is always held to its scopes, even one made by an administrator, and it never counts as an organization owner or administrator: what only they may do (giving or taking the Admin standing, editing an administrator, editing the default roles) is refused with **403 `ADMIN_ONLY`**. A key can never grant more than it holds: a role, invitation or team membership that would hand out a permission the key does not hold is **403 `FORBIDDEN`**, naming the permission. A refusal is never 401. **What a key cannot do.** Signing in and everything a person does to their own account (profile, password, two-factor authentication, sessions, connected accounts, devices, switching or creating organizations, accepting invitations), managing API keys and agents, the login policy, and deleting the organization are for a person signed in to the console. Those routes are listed in `x-route-check.not-public` with the reason; a key that calls one gets **403**. **Responses.** Success is `{"data": …, "success": true}`. Errors are `{"error": {"code", "message", "request_id"}}`; `VALIDATION_FAILED` adds `details`. `request_id` is the `X-Request-ID` of the response — quote it to support. The codes are `VALIDATION_ERROR` (400, the message names the field), `VALIDATION_FAILED`, `INVALID_BODY`, `UNKNOWN_PERMISSION`, `NOT_FOUND`, `FORBIDDEN`, `ADMIN_ONLY`, `NOT_A_PERSON`, `OWNER_ROLE`, `OWN_MEMBERSHIP`, `BUILT_IN_ROLE`, `ROLE_NAME_TAKEN`, `TEAM_NAME_TAKEN`, `INVITATION_PENDING`, `ALREADY_USED`, `SLUG_TAKEN` and `INTERNAL_ERROR`. Times are RFC 3339 in UTC. Ids carry a type prefix (`usr_`, `team_`, `role_`, `inv_`, `org_`). **Chat apps** (Slack, Microsoft Teams) are connected in On-Call: see the On-Call API reference. ## Servers - `https://evohub.io` ## Authentication - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Members The people in the organization. - [GET /api/v1/users](https://docs.evohub.io/organization/list-members.md): List members - [GET /api/v1/users/{id}](https://docs.evohub.io/organization/get-member.md): Get a member - [DELETE /api/v1/users/{id}](https://docs.evohub.io/organization/remove-member.md): Remove a member - [PATCH /api/v1/users/{id}](https://docs.evohub.io/organization/update-member.md): Update a member - [GET /api/v1/users/{userId}/roles](https://docs.evohub.io/organization/list-member-roles.md): List a member's roles ## Me Who the key belongs to, and their own settings. - [GET /api/v1/users/me](https://docs.evohub.io/organization/get-me.md): Who the key belongs to - [GET /api/v1/users/me/preferences](https://docs.evohub.io/organization/get-my-preferences.md): Get email preferences - [GET /api/v1/users/me/preferences/dashboard](https://docs.evohub.io/organization/get-my-dashboard.md): Get the console home layout ## Teams Teams and their members. - [GET /api/v1/teams](https://docs.evohub.io/organization/list-teams.md): List teams - [POST /api/v1/teams](https://docs.evohub.io/organization/create-team.md): Create a team - [DELETE /api/v1/teams/{id}](https://docs.evohub.io/organization/delete-team.md): Delete a team - [PATCH /api/v1/teams/{id}](https://docs.evohub.io/organization/update-team.md): Update a team - [GET /api/v1/teams/{id}/members](https://docs.evohub.io/organization/list-team-members.md): List a team's members - [POST /api/v1/teams/{id}/members](https://docs.evohub.io/organization/add-team-member.md): Add a member to a team - [DELETE /api/v1/teams/{id}/members/{userId}](https://docs.evohub.io/organization/remove-team-member.md): Remove a member from a team - [GET /api/v1/teams/{id}/roles](https://docs.evohub.io/organization/list-team-roles.md): List a team's roles ## Roles Custom roles, the permission catalog, and who holds which role. - [GET /api/v1/permissions](https://docs.evohub.io/organization/list-permissions.md): List the permission catalog - [GET /api/v1/roles](https://docs.evohub.io/organization/list-roles.md): List roles - [POST /api/v1/roles](https://docs.evohub.io/organization/create-role.md): Create a role - [DELETE /api/v1/roles/{id}](https://docs.evohub.io/organization/delete-role.md): Delete a role - [PATCH /api/v1/roles/{id}](https://docs.evohub.io/organization/update-role.md): Update a role - [GET /api/v1/roles/{id}/users](https://docs.evohub.io/organization/list-role-users.md): List who holds a role directly - [POST /api/v1/roles/{id}/users](https://docs.evohub.io/organization/assign-role-to-user.md): Give a role to a member - [DELETE /api/v1/roles/{id}/users/{userId}](https://docs.evohub.io/organization/unassign-role-from-user.md): Take a role from a member - [GET /api/v1/roles/{id}/teams](https://docs.evohub.io/organization/list-role-teams.md): List the teams holding a role - [POST /api/v1/roles/{id}/teams](https://docs.evohub.io/organization/assign-role-to-team.md): Give a role to a team - [DELETE /api/v1/roles/{id}/teams/{teamId}](https://docs.evohub.io/organization/unassign-role-from-team.md): Take a role from a team ## Invitations Invite people by email. - [GET /api/v1/invitations](https://docs.evohub.io/organization/list-invitations.md): List invitations - [POST /api/v1/invitations](https://docs.evohub.io/organization/create-invitation.md): Invite someone - [POST /api/v1/invitations/{id}/resend](https://docs.evohub.io/organization/resend-invitation.md): Resend an invitation - [DELETE /api/v1/invitations/{id}](https://docs.evohub.io/organization/delete-invitation.md): Cancel an invitation ## Organization The organization's name and settings. - [GET /api/v1/organizations](https://docs.evohub.io/organization/get-organization.md): Get the organization - [PATCH /api/v1/organizations/{id}](https://docs.evohub.io/organization/update-organization.md): Rename the organization ## Audit log Who changed what in the organization. - [GET /api/v1/audit-logs](https://docs.evohub.io/organization/list-audit-logs.md): List the audit log --- Source: https://docs.evohub.io/organization/list-members.md # List members `GET https://evohub.io/api/v1/users` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `listMembers`. Every member of the organization, in the order they joined. With `permission`, only the members who hold that permission (through their standing, a role or a team): the people a picker should offer for a job. **Permission (API-key scope):** `identity:user:read`. ## Authorization Any one of: - `bearerAuth` (identity:user:read) - `apiKeyHeader` (identity:user:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `permission` (string, example `oncall:alert:respond`): A permission from the catalog, for example `oncall:alert:respond`. ## Responses ### 200 — The members. Content type: `application/json` Type: `object` - `data` (array of Member, required) - `id` (string, required) - `email` (string (email), required) - `name` (string, required) - `title` (string) - `role` (Standing, required, one of `owner`, `admin`, `member`, `viewer`) - `org_id` (string, required) - `status` (string, required, one of `active`, `invited`, `suspended`) - `mfa_enabled` (boolean) - `mfa_setup_required` (boolean): Only on `/users/me`: the login policy requires MFA and the person has not set it up. - `has_password` (boolean): `false` for people who sign in only with GitHub or Google. - `phone_number` (string) - `phone_verified` (boolean) - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 400 — `permission` is not in the catalog (`UNKNOWN_PERMISSION`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/users' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/get-member.md # Get a member `GET https://evohub.io/api/v1/users/{id}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `getMember`. A member's name, email and phone. **Permission (API-key scope):** `identity:user:read`. ## Authorization Any one of: - `bearerAuth` (identity:user:read) - `apiKeyHeader` (identity:user:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Responses ### 200 — The member. Content type: `application/json` Type: `object` - `data` (MemberCard, required) - `id` (string) - `name` (string) - `email` (string (email)) - `phone_number` (string) - `phone_verified` (boolean) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such user in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/users/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/remove-member.md # Remove a member `DELETE https://evohub.io/api/v1/users/{id}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `removeMember`. Removes the person from the organization. Their account and their other organizations are untouched. The owner cannot be removed, and only an administrator removes an administrator. **Permission (API-key scope):** `identity:user:write`. ## Authorization Any one of: - `bearerAuth` (identity:user:write) - `apiKeyHeader` (identity:user:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`), or this needs an administrator (`ADMIN_ONLY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such user in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — The owner cannot be removed (`OWNER_ROLE`), and a personal key cannot remove its own person (`OWN_MEMBERSHIP`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/users/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/update-member.md # Update a member `PATCH https://evohub.io/api/v1/users/{id}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `updateMember`. Changes a member's name, title or standing. A field left out keeps its value; `title: ""` clears the title, and an empty `name` is refused (`VALIDATION_FAILED`). The name and title are the person's own across every organization they belong to. A key cannot change a standing, nor edit an owner or administrator (`ADMIN_ONLY`): that is an administrator's act. `owner` is not a value `role` takes (`VALIDATION_ERROR`), and the owner's standing cannot be changed (`OWNER_ROLE`). **Permission (API-key scope):** `identity:user:write`. ## Authorization Any one of: - `bearerAuth` (identity:user:write) - `apiKeyHeader` (identity:user:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Request body (required) Content type: `application/json` Type: `object` - `name` (string) - `title` (string) - `role` (string, one of `admin`, `member`, `viewer`) ## Responses ### 200 — The member. Content type: `application/json` Type: `object` - `data` (Member, required) - `id` (string, required) - `email` (string (email), required) - `name` (string, required) - `title` (string) - `role` (Standing, required, one of `owner`, `admin`, `member`, `viewer`) - `org_id` (string, required) - `status` (string, required, one of `active`, `invited`, `suspended`) - `mfa_enabled` (boolean) - `mfa_setup_required` (boolean): Only on `/users/me`: the login policy requires MFA and the person has not set it up. - `has_password` (boolean): `false` for people who sign in only with GitHub or Google. - `phone_number` (string) - `phone_verified` (boolean) - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 400 — Not JSON (`INVALID_BODY`), `role` is not admin, member or viewer (`VALIDATION_ERROR`), or `name` is sent empty (`VALIDATION_FAILED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`), or this needs an administrator (`ADMIN_ONLY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such user in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — The owner cannot be changed (`OWNER_ROLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/users/string' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "title": "Staff SRE" }' ``` --- Source: https://docs.evohub.io/organization/list-member-roles.md # List a member's roles `GET https://evohub.io/api/v1/users/{userId}/roles` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `listMemberRoles`. The roles given to the member directly. Roles held through a team are on `GET /api/v1/teams/{id}/roles`. An unknown member gives `[]`. **Permission (API-key scope):** `identity:role:read`. ## Authorization Any one of: - `bearerAuth` (identity:role:read) - `apiKeyHeader` (identity:role:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `userId` (string, required, example `usr_7a3c9e10-b2d4-4f6a-8c1e-5d7f9a0b2c3e`): A member's id (`usr_…`). ## Responses ### 200 — The roles. Content type: `application/json` Type: `object` - `data` (array of Role, required) - `id` (string, required) - `org_id` (string, required) - `name` (string, required) - `description` (string) - `permissions` (array of string, required): Permission names from the catalog (`GET /api/v1/permissions`). - `built_in` (string, one of `admin`, `member`, `viewer`): Set on the three default roles, which cannot be deleted and only an administrator edits. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/users/usr_7a3c9e10-b2d4-4f6a-8c1e-5d7f9a0b2c3e/roles' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/get-me.md # Who the key belongs to `GET https://evohub.io/api/v1/users/me` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `getMe`. The person a personal key belongs to, in the key's organization, with `roles` and `permissions`. `roles` is what the key acts with (`org:member` for every key); `permissions` is worked out for the person, so it lists what the person holds, not the key's scopes. An organization key belongs to no person: it gets **403 `NOT_A_PERSON`**, which is a working key, not a bad one. **Permission:** none — any valid API key of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The key's person. Content type: `application/json` Type: `object` - `data` (Member & object, required) - `id` (string, required) - `email` (string (email), required) - `name` (string, required) - `title` (string) - `role` (Standing, required, one of `owner`, `admin`, `member`, `viewer`) - `org_id` (string, required) - `status` (string, required, one of `active`, `invited`, `suspended`) - `mfa_enabled` (boolean) - `mfa_setup_required` (boolean): Only on `/users/me`: the login policy requires MFA and the person has not set it up. - `has_password` (boolean): `false` for people who sign in only with GitHub or Google. - `phone_number` (string) - `phone_verified` (boolean) - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `roles` (array of string) - `permissions` (array of string) - `teams` (array of object): The teams the person is in. - `id` (string) - `name` (string) - `slug` (string) - `success` (boolean, required, value `true`) ### 401 — The key is not valid (`UNAUTHORIZED`), or its person's account no longer exists. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — An organization key (`NOT_A_PERSON`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/users/me' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/get-my-preferences.md # Get email preferences `GET https://evohub.io/api/v1/users/me/preferences` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `getMyPreferences`. Which emails the key's person receives. For an organization key, the defaults. **Permission:** none — any valid API key of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The preferences. Content type: `application/json` Type: `object` - `data` (object, required) - `user_id` (string) - `email_invitations` (boolean) - `email_alerts` (boolean) - `email_weekly_digest` (boolean) - `email_marketing` (boolean) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/users/me/preferences' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/get-my-dashboard.md # Get the console home layout `GET https://evohub.io/api/v1/users/me/preferences/dashboard` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `getMyDashboard`. The console home page layout of the key's person in this organization: which widgets show, their size and accent, and their settings. `customized` is false, with the default widgets, when nothing has been saved. Saving a layout is done in the console. **Permission:** none — any valid API key of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The layout. Content type: `application/json` Type: `object` - `data` (object, required) - `version` (integer) - `widgets` (array of object) - `id` (string) - `visible` (boolean) - `size` (integer, min 0, max 3): Columns of three on a wide screen; 0 is the widget's default. - `accent` (string) - `config` (object) - Other keys: any - `appearance` (object | null) - `accent` (string) - `customized` (boolean) - `updated_at` (string (date-time) | null) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/users/me/preferences/dashboard' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/list-teams.md # List teams `GET https://evohub.io/api/v1/teams` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `listTeams`. Every team in the organization. Without `identity:team:read`, only the key's own teams: the team an organization key is scoped to, or the teams a personal key's owner is in. So a key can always see the teams it belongs to. **Permission:** none — any valid API key of the organization. With `identity:team:read`, every team. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The teams. Content type: `application/json` Type: `object` - `data` (array of Team, required) - `id` (string, required) - `name` (string, required) - `slug` (string) - `description` (string) - `org_id` (string, required) - `member_count` (integer, required) - `member_ids` (array of string, required) - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/teams' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/create-team.md # Create a team `POST https://evohub.io/api/v1/teams` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `createTeam`. Creates a team, optionally with its first members. A member id that is not in the organization is skipped. **Permission (API-key scope):** `identity:team:write`. ## Authorization Any one of: - `bearerAuth` (identity:team:write) - `apiKeyHeader` (identity:team:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `name` (string, required, min length 1, max length 100) - `slug` (string) - `description` (string) - `members` (array of object) - `user_id` (string, required) - `role` (TeamMemberRole, one of `admin`, `member`, `viewer`, default `member`) ## Responses ### 201 — The team. Content type: `application/json` Type: `object` - `data` (Team, required) - `id` (string, required) - `name` (string, required) - `slug` (string) - `description` (string) - `org_id` (string, required) - `member_count` (integer, required) - `member_ids` (array of string, required) - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 400 — Not JSON (`INVALID_BODY`), or the name is empty or longer than 100 characters (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — A team with that name or slug exists (`TEAM_NAME_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/teams' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Platform", "slug": "platform", "members": [ { "role": "member", "user_id": "usr_7a3c9e10-b2d4-4f6a-8c1e-5d7f9a0b2c3e" } ], "description": "Owns the core API" }' ``` --- Source: https://docs.evohub.io/organization/delete-team.md # Delete a team `DELETE https://evohub.io/api/v1/teams/{id}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `deleteTeam`. Deletes the team. Its members stay in the organization; roles held through it are no longer held. **Permission (API-key scope):** `identity:team:write`. ## Authorization Any one of: - `bearerAuth` (identity:team:write) - `apiKeyHeader` (identity:team:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such team in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/teams/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/update-team.md # Update a team `PATCH https://evohub.io/api/v1/teams/{id}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `updateTeam`. Renames a team or changes its description. A field left out keeps its value; `description: ""` clears the description, and an empty `name` is refused. The slug does not change. **Permission (API-key scope):** `identity:team:write`. ## Authorization Any one of: - `bearerAuth` (identity:team:write) - `apiKeyHeader` (identity:team:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Request body (required) Content type: `application/json` Type: `object` - `name` (string, min length 1, max length 100) - `description` (string) ## Responses ### 200 — The team. Content type: `application/json` Type: `object` - `data` (Team, required) - `id` (string, required) - `name` (string, required) - `slug` (string) - `description` (string) - `org_id` (string, required) - `member_count` (integer, required) - `member_ids` (array of string, required) - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 400 — Not JSON (`INVALID_BODY`), or a name longer than 100 characters (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such team in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — Another team has that name (`TEAM_NAME_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/teams/string' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "description": "Owns the core API and its database" }' ``` --- Source: https://docs.evohub.io/organization/list-team-members.md # List a team's members `GET https://evohub.io/api/v1/teams/{id}/members` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `listTeamMembers`. The team's members with their place in it. **Permission (API-key scope):** `identity:team:read`. Not needed for the key's own team. ## Authorization Any one of: - `bearerAuth` (identity:team:read) - `apiKeyHeader` (identity:team:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Responses ### 200 — The members. Content type: `application/json` Type: `object` - `data` (array of TeamMember, required) - `team_id` (string) - `user_id` (string) - `user_name` (string) - `user_email` (string (email)) - `role` (TeamMemberRole, one of `admin`, `member`, `viewer`, default `member`) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such team in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/teams/string/members' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/add-team-member.md # Add a member to a team `POST https://evohub.io/api/v1/teams/{id}/members` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `addTeamMember`. Adds a member of the organization to the team, or changes their place in it. A seat in a team is every role the team holds, so the key must hold every permission those roles carry (`FORBIDDEN`, naming the first it lacks), and a team holding the Admin role takes an administrator (`ADMIN_ONLY`). **Permission (API-key scope):** `identity:team:write`. ## Authorization Any one of: - `bearerAuth` (identity:team:write) - `apiKeyHeader` (identity:team:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Request body (required) Content type: `application/json` Type: `object` - `user_id` (string, required) - `role` (TeamMemberRole, one of `admin`, `member`, `viewer`, default `member`) ## Responses ### 201 — Added. Content type: `application/json` Type: `object` - `data` (object, required) - `status` (string, value `member_added`) - `success` (boolean, required, value `true`) ### 400 — Not JSON (`INVALID_BODY`), or `user_id` is missing (`VALIDATION_FAILED`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The response's `X-Request-ID`; quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`); the change would grant a permission the key does not hold (`FORBIDDEN`, naming it); or only an administrator may do it (`ADMIN_ONLY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such team, or the person is not a member of the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/teams/string/members' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "role": "member", "user_id": "usr_7a3c9e10-b2d4-4f6a-8c1e-5d7f9a0b2c3e" }' ``` --- Source: https://docs.evohub.io/organization/remove-team-member.md # Remove a member from a team `DELETE https://evohub.io/api/v1/teams/{id}/members/{userId}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `removeTeamMember`. Takes the person out of the team, and with it the roles they held through it. **Permission (API-key scope):** `identity:team:write`. ## Authorization Any one of: - `bearerAuth` (identity:team:write) - `apiKeyHeader` (identity:team:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. - `userId` (string, required, example `usr_7a3c9e10-b2d4-4f6a-8c1e-5d7f9a0b2c3e`): A member's id (`usr_…`). ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — Not a member of that team (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/teams/string/members/usr_7a3c9e10-b2d4-4f6a-8c1e-5d7f9a0b2c3e' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/list-team-roles.md # List a team's roles `GET https://evohub.io/api/v1/teams/{id}/roles` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `listTeamRoles`. The roles the team holds; everyone in the team holds them. A team that is not your organization's — unknown, or another organization's — is 404 `NOT_FOUND`. **Permission (API-key scope):** `identity:team:read`. ## Authorization Any one of: - `bearerAuth` (identity:team:read) - `apiKeyHeader` (identity:team:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Responses ### 200 — The roles. Content type: `application/json` Type: `object` - `data` (array of Role, required) - `id` (string, required) - `org_id` (string, required) - `name` (string, required) - `description` (string) - `permissions` (array of string, required): Permission names from the catalog (`GET /api/v1/permissions`). - `built_in` (string, one of `admin`, `member`, `viewer`): Set on the three default roles, which cannot be deleted and only an administrator edits. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No team with this id in your organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/teams/string/roles' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/list-permissions.md # List the permission catalog `GET https://evohub.io/api/v1/permissions` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `listPermissions`. Every permission a role or a key can carry, split into service, resource and action, with `grantable` saying whether the caller holds it. `org_id` naming another organization is for a person signed in to the console choosing which organization a new key is for; an API key answers only for its own organization and gets **403** for any other. **Permission:** none — any valid API key of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `org_id` (string): The key's own organization (`org_…`); any other is 403 for an API key. ## Responses ### 200 — The catalog. Content type: `application/json` Type: `object` - `data` (array of Permission, required) - `permission` (string) - `service` (string) - `resource` (string) - `action` (string) - `deprecated` (boolean): An older name still accepted, no longer offered. - `grantable` (boolean): Whether the caller holds it, and so may put it in a role or on a key. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — `org_id` is not the key's own organization (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/permissions' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/list-roles.md # List roles `GET https://evohub.io/api/v1/roles` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `listRoles`. The default roles (Admin, Member, Viewer) and the organization's custom roles. **Permission (API-key scope):** `identity:role:read`. ## Authorization Any one of: - `bearerAuth` (identity:role:read) - `apiKeyHeader` (identity:role:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The roles. Content type: `application/json` Type: `object` - `data` (array of Role, required) - `id` (string, required) - `org_id` (string, required) - `name` (string, required) - `description` (string) - `permissions` (array of string, required): Permission names from the catalog (`GET /api/v1/permissions`). - `built_in` (string, one of `admin`, `member`, `viewer`): Set on the three default roles, which cannot be deleted and only an administrator edits. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/roles' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/create-role.md # Create a role `POST https://evohub.io/api/v1/roles` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `createRole`. Creates a custom role. Every permission must be in the catalog and held by the key. **Permission (API-key scope):** `identity:role:write`. ## Authorization Any one of: - `bearerAuth` (identity:role:write) - `apiKeyHeader` (identity:role:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `RoleWrite` - `name` (string, required, min length 1, max length 100) - `description` (string) - `permissions` (array of string): Each must be in the catalog (else 400 `UNKNOWN_PERMISSION`) and held by the key (else 403). ## Responses ### 201 — The role. Content type: `application/json` Type: `object` - `data` (Role, required) - `id` (string, required) - `org_id` (string, required) - `name` (string, required) - `description` (string) - `permissions` (array of string, required): Permission names from the catalog (`GET /api/v1/permissions`). - `built_in` (string, one of `admin`, `member`, `viewer`): Set on the three default roles, which cannot be deleted and only an administrator edits. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 400 — Not JSON (`INVALID_BODY`), no name or one over 100 characters (`VALIDATION_ERROR`), or a permission not in the catalog (`UNKNOWN_PERMISSION`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`); the change would grant a permission the key does not hold (`FORBIDDEN`, naming it); or only an administrator may do it (`ADMIN_ONLY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — A role with that name exists (`ROLE_NAME_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/roles' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Incident responder", "description": "Answers alerts and runs incidents", "permissions": [ "oncall:alert:read", "oncall:alert:respond", "oncall:incident:write" ] }' ``` --- Source: https://docs.evohub.io/organization/delete-role.md # Delete a role `DELETE https://evohub.io/api/v1/roles/{id}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `deleteRole`. Deletes a custom role; everyone loses what it gave them. A default role cannot be deleted. **Permission (API-key scope):** `identity:role:write`. ## Authorization Any one of: - `bearerAuth` (identity:role:write) - `apiKeyHeader` (identity:role:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such role in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — A default role (`BUILT_IN_ROLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/roles/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/update-role.md # Update a role `PATCH https://evohub.io/api/v1/roles/{id}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `updateRole`. Replaces the role's name, description and permissions: send all three (`name` is required; a `permissions` left out keeps the list). Every holder's access changes at once. Permissions added must be held by the key; a default role is edited only by an administrator (`ADMIN_ONLY`). **Permission (API-key scope):** `identity:role:write`. ## Authorization Any one of: - `bearerAuth` (identity:role:write) - `apiKeyHeader` (identity:role:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Request body (required) Content type: `application/json` Type: `RoleWrite` - `name` (string, required, min length 1, max length 100) - `description` (string) - `permissions` (array of string): Each must be in the catalog (else 400 `UNKNOWN_PERMISSION`) and held by the key (else 403). ## Responses ### 200 — The role. Content type: `application/json` Type: `object` - `data` (Role, required) - `id` (string, required) - `org_id` (string, required) - `name` (string, required) - `description` (string) - `permissions` (array of string, required): Permission names from the catalog (`GET /api/v1/permissions`). - `built_in` (string, one of `admin`, `member`, `viewer`): Set on the three default roles, which cannot be deleted and only an administrator edits. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 400 — Not JSON, no name, or an unknown permission (`INVALID_BODY`, `VALIDATION_ERROR`, `UNKNOWN_PERMISSION`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`); the change would grant a permission the key does not hold (`FORBIDDEN`, naming it); or only an administrator may do it (`ADMIN_ONLY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such role in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — Another role has that name (`ROLE_NAME_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/roles/string' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Incident responder", "description": "Answers alerts, runs incidents and writes postmortems", "permissions": [ "oncall:alert:read", "oncall:alert:respond", "oncall:incident:write", "oncall:postmortem:write" ] }' ``` --- Source: https://docs.evohub.io/organization/list-role-users.md # List who holds a role directly `GET https://evohub.io/api/v1/roles/{id}/users` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `listRoleUsers`. The ids of the members given the role directly (not through a team). **Permission (API-key scope):** `identity:role:read`. ## Authorization Any one of: - `bearerAuth` (identity:role:read) - `apiKeyHeader` (identity:role:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Responses ### 200 — Member ids. Content type: `application/json` Type: `object` - `data` (array of string, required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such role in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/roles/string/users' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/assign-role-to-user.md # Give a role to a member `POST https://evohub.io/api/v1/roles/{id}/users` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `assignRoleToUser`. Gives the member the role. The key must hold every permission in it; the Admin role is given only by an administrator, and the owner's roles do not change. **Permission (API-key scope):** `identity:role:write`. ## Authorization Any one of: - `bearerAuth` (identity:role:write) - `apiKeyHeader` (identity:role:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Request body (required) Content type: `application/json` Type: `object` - `user_id` (string, required) ## Responses ### 204 — Given. ### 400 — Not JSON (`INVALID_BODY`), or no `user_id` (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`); the change would grant a permission the key does not hold (`FORBIDDEN`, naming it); or only an administrator may do it (`ADMIN_ONLY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such role in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — The member is the owner (`OWNER_ROLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/roles/string/users' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "user_id": "usr_7a3c9e10-b2d4-4f6a-8c1e-5d7f9a0b2c3e" }' ``` --- Source: https://docs.evohub.io/organization/unassign-role-from-user.md # Take a role from a member `DELETE https://evohub.io/api/v1/roles/{id}/users/{userId}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `unassignRoleFromUser`. Takes a directly given role from the member. Taking the Admin role is an administrator's act. **Permission (API-key scope):** `identity:role:write`. ## Authorization Any one of: - `bearerAuth` (identity:role:write) - `apiKeyHeader` (identity:role:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. - `userId` (string, required, example `usr_7a3c9e10-b2d4-4f6a-8c1e-5d7f9a0b2c3e`): A member's id (`usr_…`). ## Responses ### 204 — Taken. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`), or it is the Admin role (`ADMIN_ONLY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — The member does not hold the role directly (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — The member is the owner (`OWNER_ROLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/roles/string/users/usr_7a3c9e10-b2d4-4f6a-8c1e-5d7f9a0b2c3e' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/list-role-teams.md # List the teams holding a role `GET https://evohub.io/api/v1/roles/{id}/teams` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `listRoleTeams`. The ids of the teams that hold the role. **Permission (API-key scope):** `identity:role:read`. ## Authorization Any one of: - `bearerAuth` (identity:role:read) - `apiKeyHeader` (identity:role:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Responses ### 200 — Team ids. Content type: `application/json` Type: `object` - `data` (array of string, required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such role in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/roles/string/teams' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/assign-role-to-team.md # Give a role to a team `POST https://evohub.io/api/v1/roles/{id}/teams` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `assignRoleToTeam`. Gives the role to a team: whoever is in the team holds it, for as long as they are in it. The key must hold every permission in the role; the Admin role is given only by an administrator. **Permission (API-key scope):** `identity:role:write`. ## Authorization Any one of: - `bearerAuth` (identity:role:write) - `apiKeyHeader` (identity:role:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Request body (required) Content type: `application/json` Type: `object` - `team_id` (string, required) ## Responses ### 204 — Given. ### 400 — Not JSON (`INVALID_BODY`), or no `team_id` (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope (`FORBIDDEN`); the change would grant a permission the key does not hold (`FORBIDDEN`, naming it); or only an administrator may do it (`ADMIN_ONLY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such role in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/roles/string/teams' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "team_id": "team_2f9c4b7e-1a3d-4c5e-9f7a-0b2d4e6f8a1c" }' ``` --- Source: https://docs.evohub.io/organization/unassign-role-from-team.md # Take a role from a team `DELETE https://evohub.io/api/v1/roles/{id}/teams/{teamId}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `unassignRoleFromTeam`. The team's members stop holding the role through it. **Permission (API-key scope):** `identity:role:write`. ## Authorization Any one of: - `bearerAuth` (identity:role:write) - `apiKeyHeader` (identity:role:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. - `teamId` (string, required, example `team_2f9c4b7e-1a3d-4c5e-9f7a-0b2d4e6f8a1c`): A team's id (`team_…`). ## Responses ### 204 — Taken. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — The team does not hold the role (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/roles/string/teams/team_2f9c4b7e-1a3d-4c5e-9f7a-0b2d4e6f8a1c' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/list-invitations.md # List invitations `GET https://evohub.io/api/v1/invitations` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `listInvitations`. Every invitation the organization has sent, with its status. The invitation token is left out (`""`). **Permission (API-key scope):** `identity:user:invite`. ## Authorization Any one of: - `bearerAuth` (identity:user:invite) - `apiKeyHeader` (identity:user:invite) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The invitations. Content type: `application/json` Type: `object` - `data` (array of Invitation, required) - `id` (string, required) - `org_id` (string, required) - `email` (string (email), required) - `role` (string, required, one of `admin`, `member`, `viewer`) - `role_ids` (array of string | null): Roles held on arrival. - `team_ids` (array of string | null): Teams joined on arrival. - `token` (string): The secret the invitation email carries. Always empty (`""`) in a response to the organization; only the email carries it. - `status` (string, required, one of `pending`, `accepted`, `declined`, `expired`) - `invited_by` (string, required) - `expires_at` (string (date-time), required): Seven days after it was sent (or last resent). - `accepted_at` (string (date-time)) - `created_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/invitations' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/create-invitation.md # Invite someone `POST https://evohub.io/api/v1/invitations` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `createInvitation`. Emails an invitation to join the organization, valid for seven days. `role` is the standing (default `member`); `role_ids` and `team_ids` are what the person gets on arrival. The key must hold every permission those carry, and only an administrator invites an administrator. An invitation names who sent it, so it needs a **personal** key: an organization key gets **403 `NOT_A_PERSON`**. The answer's `token` is empty (`""`): the email is the only place the invitation's secret goes. **Permission (API-key scope):** `identity:user:invite`. ## Authorization Any one of: - `bearerAuth` (identity:user:invite) - `apiKeyHeader` (identity:user:invite) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `email` (string (email), required) - `role` (string, one of `admin`, `member`, `viewer`, default `member`) - `role_ids` (array of string) - `team_ids` (array of string) ## Responses ### 201 — The invitation. Content type: `application/json` Type: `object` - `data` (Invitation, required) - `id` (string, required) - `org_id` (string, required) - `email` (string (email), required) - `role` (string, required, one of `admin`, `member`, `viewer`) - `role_ids` (array of string | null): Roles held on arrival. - `team_ids` (array of string | null): Teams joined on arrival. - `token` (string): The secret the invitation email carries. Always empty (`""`) in a response to the organization; only the email carries it. - `status` (string, required, one of `pending`, `accepted`, `declined`, `expired`) - `invited_by` (string, required) - `expires_at` (string (date-time), required): Seven days after it was sent (or last resent). - `accepted_at` (string (date-time)) - `created_at` (string (date-time), required) - `success` (boolean, required, value `true`) ### 400 — Not JSON (`INVALID_BODY`), no email or one that is not a plain address with a dotted domain (`VALIDATION_FAILED`, field `email`), or `role` is not admin, member or viewer (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope or a permission the invitation grants (`FORBIDDEN`), an administrator is being invited (`ADMIN_ONLY`), or it is an organization key (`NOT_A_PERSON`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — A role or team named does not exist (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — The address already has a pending invitation (`INVITATION_PENDING`); resend it instead. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/invitations' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "role": "member", "email": "deniz@acme.example", "role_ids": [ "role_8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b" ], "team_ids": [ "team_2f9c4b7e-1a3d-4c5e-9f7a-0b2d4e6f8a1c" ] }' ``` --- Source: https://docs.evohub.io/organization/resend-invitation.md # Resend an invitation `POST https://evohub.io/api/v1/invitations/{id}/resend` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `resendInvitation`. Mails the invitation again with a new link and gives it another seven days. The link in the earlier email stops working. **Permission (API-key scope):** `identity:user:invite`. ## Authorization Any one of: - `bearerAuth` (identity:user:invite) - `apiKeyHeader` (identity:user:invite) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Responses ### 200 — Resent. Content type: `application/json` Type: `object` - `data` (object, required) - `status` (string, value `resent`) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such invitation (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — Already accepted or declined (`ALREADY_USED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/invitations/string/resend' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/delete-invitation.md # Cancel an invitation `DELETE https://evohub.io/api/v1/invitations/{id}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `deleteInvitation`. Withdraws the invitation; its link stops working. **Permission (API-key scope):** `identity:user:invite`. ## Authorization Any one of: - `bearerAuth` (identity:user:invite) - `apiKeyHeader` (identity:user:invite) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Responses ### 200 — Cancelled. Note: **200** with a body, not 204. Content type: `application/json` Type: `object` - `data` (object, required) - `status` (string, value `deleted`) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such invitation (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/invitations/string' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/get-organization.md # Get the organization `GET https://evohub.io/api/v1/organizations` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `getOrganization`. The key's organization. **Permission:** none — any valid API key of the organization. ## Authorization Any one of: - `bearerAuth` - `apiKeyHeader` Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The organization. Content type: `application/json` Type: `object` - `data` (Organization, required) - `id` (string) - `name` (string) - `slug` (string) - `plan` (string, one of `free`, `pro`, `enterprise`) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — The organization no longer exists (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/organizations' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/organization/update-organization.md # Rename the organization `PATCH https://evohub.io/api/v1/organizations/{id}` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `updateOrganization`. Changes the organization's name or slug. `id` must be the key's own organization. A field left out keeps its value; sent empty it is refused (`VALIDATION_FAILED`). A slug another organization uses is **409 `SLUG_TAKEN`**. **Permission (API-key scope):** `identity:org:write`. ## Authorization Any one of: - `bearerAuth` (identity:org:write) - `apiKeyHeader` (identity:org:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required): The id of the member (`usr_…`), team (`team_…`), role (`role_…`), invitation (`inv_…`) or organization (`org_…`) the path names. ## Request body (required) Content type: `application/json` Type: `object` - `name` (string) - `slug` (string) ## Responses ### 200 — The organization. Content type: `application/json` Type: `object` - `data` (Organization, required) - `id` (string) - `name` (string) - `slug` (string) - `plan` (string, one of `free`, `pro`, `enterprise`) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — The body is not JSON (`INVALID_BODY`), or `name` or `slug` is sent empty (`VALIDATION_FAILED`, with `details`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string): The response's `X-Request-ID`; quote it to support. - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope, or `id` is another organization (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 404 — No such organization in the organization (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 409 — Another organization uses that slug (`SLUG_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/organizations/string' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Acme Inc." }' ``` --- Source: https://docs.evohub.io/organization/list-audit-logs.md # List the audit log `GET https://evohub.io/api/v1/audit-logs` Part of the [Organization API](https://docs.evohub.io/organization.md) reference · operationId `listAuditLogs`. Who changed what in the organization's members, teams, roles, invitations, keys and settings, and who signed in, newest first. Paged with `limit` and `offset`. The other products keep their own logs; EvoTrail reads them all together. **Permission (API-key scope):** `identity:audit:read`. ## Authorization Any one of: - `bearerAuth` (identity:audit:read) - `apiKeyHeader` (identity:audit:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `limit` (integer, default `50`, min 1, max 200): Page size. Over 200 (or not a number) gives 50. - `offset` (integer, default `0`, min 0) - `exclude` (string): Comma-separated actions to leave out, for example `user.login,user.logout`. ## Responses ### 200 — The entries. Content type: `application/json` Type: `object` - `data` (array of AuditLog, required) - `id` (string) - `org_id` (string) - `actor_id` (string): The user or API key that did it. - `actor_name` (string) - `action` (string): `resource.verb`, for example `user.role_change`, `team.member_add`, `role.assign`, `invitation.create`, `org.settings_update`, `api_key.create`, `user.login`. - `resource` (string) - `resource_id` (string) - `metadata` (object) - Other keys: any - `request_id` (string) - `ip` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): The response's `X-Request-ID`; quote it to support. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/audit-logs' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro.md # Retro API Version 1.0 · OpenAPI 3.1.0 Run EvoHub Retro from scripts and integrations: create retro boards, shape their columns, add and moderate cards, vote, react and comment, track action items, control a board's shared timer and sharing, export a board to PDF, and read retro analytics and the audit log. **Authentication.** Every endpoint takes an EvoHub API key, sent as `Authorization: Bearer evohub_…` or as `X-API-Key: evohub_…`. Each operation names the scope the key needs in its description and in `x-evohub-permission`. A missing or invalid key gets **401**; a valid key without the scope gets **403**, never 401. A key holds exactly its scopes: it is never treated as an administrator. See the [API overview](/api-overview), [API keys and scopes](/api-keys-and-scopes) and [Errors](/errors). **Responses.** A successful answer carries `"success": true` and its result in `data`. Errors carry `"success": false` and an `error` object with a machine-readable `code` and, usually, the `request_id` to quote to support. Timestamps are RFC 3339 in UTC. **Who sees a board.** A board belongs to the whole organization (`team_id` is `""`) or to one team, and its `visibility` says who sees it without being named on it: `org` (everyone), `team` (that team) or `private` (nobody). People named on a board, and teams the board was shared with, see it whatever its visibility. An organization key sees as its team; a personal key sees as the person who made it. A board the caller cannot see answers **404** `NOT_FOUND`, exactly like one that does not exist. **Closed boards.** Closing a retro freezes it: columns, cards, comments, reactions, votes, action items and the timer can no longer be changed, and those writes answer **409** `BOARD_CLOSED`. A closed board can still be read, renamed, reconfigured, reshared, reopened and deleted. **Anonymity.** Cards and comments never say who wrote them. The author is kept only to decide who may edit or delete them: content a key writes is the key's own (a personal key's is its person's), and changing anyone else's needs `retro:board:write`. **Share links.** Guests who open a board's share link use the board through the web app, with the link as their credential. This reference is for API-key callers; see [Retro boards](/retro-boards) for share links. ## Servers - `https://evohub.io` ## Authentication - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Boards Create, list, read, change, close, reopen and delete retro boards. - [GET /api/v1/retro/boards](https://docs.evohub.io/retro/list-retro-boards.md): List boards - [POST /api/v1/retro/boards](https://docs.evohub.io/retro/create-retro-board.md): Create a board - [GET /api/v1/retro/boards/{boardID}](https://docs.evohub.io/retro/get-retro-board.md): Get a board - [DELETE /api/v1/retro/boards/{boardID}](https://docs.evohub.io/retro/delete-retro-board.md): Delete a board - [PATCH /api/v1/retro/boards/{boardID}](https://docs.evohub.io/retro/update-retro-board.md): Rename or move a board - [POST /api/v1/retro/boards/{boardID}/close](https://docs.evohub.io/retro/close-retro-board.md): Close a board - [POST /api/v1/retro/boards/{boardID}/reopen](https://docs.evohub.io/retro/reopen-retro-board.md): Reopen a board ## Sharing The people and teams a board is shared with, and its share link. - [GET /api/v1/retro/boards/{boardID}/members](https://docs.evohub.io/retro/list-retro-board-members.md): List who a board is shared with - [POST /api/v1/retro/boards/{boardID}/members](https://docs.evohub.io/retro/add-retro-board-member.md): Add a person to a board - [DELETE /api/v1/retro/boards/{boardID}/members/{userID}](https://docs.evohub.io/retro/remove-retro-board-member.md): Remove a person from a board - [PATCH /api/v1/retro/boards/{boardID}/share](https://docs.evohub.io/retro/update-retro-share-mode.md): Set the share mode - [POST /api/v1/retro/boards/{boardID}/share/rotate](https://docs.evohub.io/retro/rotate-retro-share-token.md): Rotate the share link - [POST /api/v1/retro/boards/{boardID}/teams](https://docs.evohub.io/retro/share-retro-board-with-team.md): Share a board with a team - [DELETE /api/v1/retro/boards/{boardID}/teams/{teamID}](https://docs.evohub.io/retro/unshare-retro-board-from-team.md): Stop sharing a board with a team ## Facilitation A board's settings and its shared countdown timer. - [PATCH /api/v1/retro/boards/{boardID}/settings](https://docs.evohub.io/retro/update-retro-board-settings.md): Change a board's settings - [POST /api/v1/retro/boards/{boardID}/timer](https://docs.evohub.io/retro/control-retro-timer.md): Control the board's timer ## Columns The columns that give a board its shape. - [POST /api/v1/retro/boards/{boardID}/columns](https://docs.evohub.io/retro/create-retro-column.md): Add a column - [DELETE /api/v1/retro/boards/{boardID}/columns/{columnID}](https://docs.evohub.io/retro/delete-retro-column.md): Delete a column - [PATCH /api/v1/retro/boards/{boardID}/columns/{columnID}](https://docs.evohub.io/retro/update-retro-column.md): Change a column ## Cards Cards, and the comments, reactions and votes on them. - [POST /api/v1/retro/boards/{boardID}/cards/merge](https://docs.evohub.io/retro/merge-retro-cards.md): Merge cards - [DELETE /api/v1/retro/boards/{boardID}/cards/{cardID}](https://docs.evohub.io/retro/delete-retro-card.md): Delete a card - [PATCH /api/v1/retro/boards/{boardID}/cards/{cardID}](https://docs.evohub.io/retro/update-retro-card.md): Edit a card - [POST /api/v1/retro/boards/{boardID}/cards/{cardID}/comments](https://docs.evohub.io/retro/create-retro-comment.md): Comment on a card - [DELETE /api/v1/retro/boards/{boardID}/cards/{cardID}/comments/{commentID}](https://docs.evohub.io/retro/delete-retro-comment.md): Delete a comment - [POST /api/v1/retro/boards/{boardID}/cards/{cardID}/reactions](https://docs.evohub.io/retro/toggle-retro-reaction.md): Add or remove a reaction - [POST /api/v1/retro/boards/{boardID}/cards/{cardID}/votes](https://docs.evohub.io/retro/vote-on-retro-card.md): Vote on a card - [POST /api/v1/retro/boards/{boardID}/columns/{columnID}/cards](https://docs.evohub.io/retro/create-retro-card.md): Add a card ## Action items What the team agreed to do, with owners and due dates. - [GET /api/v1/retro/action-items/mine](https://docs.evohub.io/retro/list-my-action-items.md): List my open action items - [GET /api/v1/retro/boards/{boardID}/action-items](https://docs.evohub.io/retro/list-retro-action-items.md): List a board's action items - [POST /api/v1/retro/boards/{boardID}/action-items](https://docs.evohub.io/retro/create-retro-action-item.md): Add an action item - [DELETE /api/v1/retro/boards/{boardID}/action-items/{itemID}](https://docs.evohub.io/retro/delete-retro-action-item.md): Delete an action item - [PATCH /api/v1/retro/boards/{boardID}/action-items/{itemID}](https://docs.evohub.io/retro/update-retro-action-item.md): Update an action item ## Export A board as a PDF. - [GET /api/v1/retro/boards/{boardID}/export](https://docs.evohub.io/retro/export-retro-board.md): Export a board as PDF ## Analytics Summary counts and retro analytics across the boards you can see. - [GET /api/v1/retro/analytics](https://docs.evohub.io/retro/get-retro-analytics.md): Get retro analytics - [GET /api/v1/retro/stats](https://docs.evohub.io/retro/get-retro-stats.md): Get board counts ## Audit log Who changed what in Retro. - [GET /api/v1/retro/audit-logs](https://docs.evohub.io/retro/list-retro-audit-logs.md): List audit log entries --- Source: https://docs.evohub.io/retro/list-retro-boards.md # List boards `GET https://evohub.io/api/v1/retro/boards` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `listRetroBoards`. Every board the caller can see, newest first, without their columns, cards or action items (read one board for those). `data` is `[]` when there are no boards. Requires the `retro:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `team_id` (string, example `none`): Narrow to one team's boards plus the organization-wide ones, or `none` for only the organization-wide ones. It never widens what the caller can see. ## Responses ### 200 — The boards. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (array of Board) - `id` (string, example `brd_3f9a1c7e2b8d4a60`) - `org_id` (string) - `name` (string) - `description` (string) - `status` (string, one of `active`, `closed`) - `team_id` (string): The team the board belongs to, or `""` for the whole organization. - `visibility` (Visibility, one of `org`, `team`, `private`) - `created_by` (string): The user ID (or, for an organization key, the key's ID) that created the board. - `created_at` (string (date-time)) - `voting_enabled` (boolean) - `comments_enabled` (boolean) - `sort_by_votes` (boolean): Every column's cards are ordered highest vote first, for everyone. - `share_mode` (ShareMode, one of `disabled`, `read_only`, `participate`) - `share_token` (string): The credential the board's share link carries. - `share_url` (string): The board's share link; left out while sharing is `disabled`. - `timer` (Timer) - `state` (string, one of `idle`, `running`, `paused`): A running timer whose end has passed stays `running` with `remaining_ms` 0 until it is stopped. - `ends_at` (string (date-time)): When a running countdown ends; left out otherwise. - `remaining_ms` (integer (int64)): Time left, in milliseconds. - `server_time` (string (date-time)): The server's clock when the timer was read. - `columns` (array of Column): Only when one board is read; left out when the board has none. - `id` (string, example `col_1a2b3c4d5e6f7a80`) - `board_id` (string) - `name` (string) - `emoji` (string) - `color` (string) - `position` (integer) - `sort_order` (integer) - `created_at` (string (date-time)) - `cards` (array of Card): Only when a whole board is read. - `action_items` (array of ActionItem): Only when one board is read; left out when the board has none. - `id` (string, example `act_5b2e8c1d9f4a7e30`) - `board_id` (string) - `description` (string) - `assignee` (string): A free-text owner name; `""` when none. - `assignee_user_id` (string | null): The organization member who owns the item. - `status` (string, one of `open`, `done`) - `created_at` (string (date-time)) - `updated_at` (string (date-time)): When the item last changed. - `completed_at` (string (date-time) | null): When the item was marked done; `null` while open, and for some items done before this was recorded. - `due_at` (string (date-time) | null) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/retro/boards' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/create-retro-board.md # Create a board `POST https://evohub.io/api/v1/retro/boards` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `createRetroBoard`. Creates an active board with no columns; add them with `POST /api/v1/retro/boards/{boardID}/columns`. Voting and comments are on, and the board gets a working share link in `participate` mode (change it with `PATCH /api/v1/retro/boards/{boardID}/share`). Whoever creates the board is named on it, so a `private` board stays visible to them. - `name` is required, at most 200 characters. - `team_id` must be a team the caller is in, or `""` (the default) for the whole organization; otherwise **403** `NOT_IN_TEAM`. - `visibility` defaults to `team` when a team is given and `org` otherwise. A `team` board needs a `team_id`. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `CreateBoardRequest` - `name` (string, required, max length 200) - `description` (string) - `team_id` (string): A team the caller is in, or `""` (the default) for the whole organization. - `visibility` (Visibility, one of `org`, `team`, `private`) ## Responses ### 201 — The board was created. Content type: `application/json` Type: `BoardEnvelope` - `success` (boolean) - `data` (Board) - `id` (string, example `brd_3f9a1c7e2b8d4a60`) - `org_id` (string) - `name` (string) - `description` (string) - `status` (string, one of `active`, `closed`) - `team_id` (string): The team the board belongs to, or `""` for the whole organization. - `visibility` (Visibility, one of `org`, `team`, `private`) - `created_by` (string): The user ID (or, for an organization key, the key's ID) that created the board. - `created_at` (string (date-time)) - `voting_enabled` (boolean) - `comments_enabled` (boolean) - `sort_by_votes` (boolean): Every column's cards are ordered highest vote first, for everyone. - `share_mode` (ShareMode, one of `disabled`, `read_only`, `participate`) - `share_token` (string): The credential the board's share link carries. - `share_url` (string): The board's share link; left out while sharing is `disabled`. - `timer` (Timer) - `state` (string, one of `idle`, `running`, `paused`): A running timer whose end has passed stays `running` with `remaining_ms` 0 until it is stopped. - `ends_at` (string (date-time)): When a running countdown ends; left out otherwise. - `remaining_ms` (integer (int64)): Time left, in milliseconds. - `server_time` (string (date-time)): The server's clock when the timer was read. - `columns` (array of Column): Only when one board is read; left out when the board has none. - `id` (string, example `col_1a2b3c4d5e6f7a80`) - `board_id` (string) - `name` (string) - `emoji` (string) - `color` (string) - `position` (integer) - `sort_order` (integer) - `created_at` (string (date-time)) - `cards` (array of Card): Only when a whole board is read. - `action_items` (array of ActionItem): Only when one board is read; left out when the board has none. - `id` (string, example `act_5b2e8c1d9f4a7e30`) - `board_id` (string) - `description` (string) - `assignee` (string): A free-text owner name; `""` when none. - `assignee_user_id` (string | null): The organization member who owns the item. - `status` (string, one of `open`, `done`) - `created_at` (string (date-time)) - `updated_at` (string (date-time)): When the item last changed. - `completed_at` (string (date-time) | null): When the item was marked done; `null` while open, and for some items done before this was recorded. - `due_at` (string (date-time) | null) ### 400 — The body is not JSON (`INVALID_BODY`), `name` is missing or blank (`MISSING_NAME`), `name` is over 200 characters (`VALIDATION_FAILED`, in the `ValidationFailed` shape), `visibility` is not `org`, `team` or `private` (`INVALID_VISIBILITY`), or a `team` board has no `team_id` (`TEAM_REQUIRED`). Content type: `application/json` Type: `Error | ValidationFailed` - One of: - Error - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) - ValidationFailed - `success` (boolean, value `false`) - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `details` (array of object, required) - `field` (string) - `message` (string) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks `retro:board:write` (`FORBIDDEN`), or `team_id` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Sprint 42 retro", "team_id": "team_6a1f3e9c2d7b4058", "description": "What went well and what to change after sprint 42" }' ``` --- Source: https://docs.evohub.io/retro/get-retro-board.md # Get a board `GET https://evohub.io/api/v1/retro/boards/{boardID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `getRetroBoard`. One board, whole: its columns in order with their cards (oldest first), each card's reactions, comments, net `vote_score` and the caller's own vote in `my_vote`, the board's action items, and its timer. Empty `columns`, `action_items`, `reactions` and `comments` are left out. Requires the `retro:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Responses ### 200 — The board. Content type: `application/json` Type: `BoardEnvelope` - `success` (boolean) - `data` (Board) - `id` (string, example `brd_3f9a1c7e2b8d4a60`) - `org_id` (string) - `name` (string) - `description` (string) - `status` (string, one of `active`, `closed`) - `team_id` (string): The team the board belongs to, or `""` for the whole organization. - `visibility` (Visibility, one of `org`, `team`, `private`) - `created_by` (string): The user ID (or, for an organization key, the key's ID) that created the board. - `created_at` (string (date-time)) - `voting_enabled` (boolean) - `comments_enabled` (boolean) - `sort_by_votes` (boolean): Every column's cards are ordered highest vote first, for everyone. - `share_mode` (ShareMode, one of `disabled`, `read_only`, `participate`) - `share_token` (string): The credential the board's share link carries. - `share_url` (string): The board's share link; left out while sharing is `disabled`. - `timer` (Timer) - `state` (string, one of `idle`, `running`, `paused`): A running timer whose end has passed stays `running` with `remaining_ms` 0 until it is stopped. - `ends_at` (string (date-time)): When a running countdown ends; left out otherwise. - `remaining_ms` (integer (int64)): Time left, in milliseconds. - `server_time` (string (date-time)): The server's clock when the timer was read. - `columns` (array of Column): Only when one board is read; left out when the board has none. - `id` (string, example `col_1a2b3c4d5e6f7a80`) - `board_id` (string) - `name` (string) - `emoji` (string) - `color` (string) - `position` (integer) - `sort_order` (integer) - `created_at` (string (date-time)) - `cards` (array of Card): Only when a whole board is read. - `action_items` (array of ActionItem): Only when one board is read; left out when the board has none. - `id` (string, example `act_5b2e8c1d9f4a7e30`) - `board_id` (string) - `description` (string) - `assignee` (string): A free-text owner name; `""` when none. - `assignee_user_id` (string | null): The organization member who owns the item. - `status` (string, one of `open`, `done`) - `created_at` (string (date-time)) - `updated_at` (string (date-time)): When the item last changed. - `completed_at` (string (date-time) | null): When the item was marked done; `null` while open, and for some items done before this was recorded. - `due_at` (string (date-time) | null) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/delete-retro-board.md # Delete a board `DELETE https://evohub.io/api/v1/retro/boards/{boardID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `deleteRetroBoard`. Deletes the board with its columns, cards, comments, reactions, votes and action items. This cannot be undone. To keep a retro but stop changes to it, close it instead. Requires the `retro:board:delete` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Responses ### 204 — The board was deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/update-retro-board.md # Rename or move a board `PATCH https://evohub.io/api/v1/retro/boards/{boardID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `updateRetroBoard`. Changes a board's name and description, and optionally its team and visibility. Works on a closed board too. - `name` is required on every call, at most 200 characters. - `description` is replaced by what you send; leaving it out clears it. - `team_id`: left out, the board stays where it is; `""` moves it to the whole organization; a team must be one the caller is in. - `visibility`: left out, it is kept. A `team` board needs a team. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Request body (required) Content type: `application/json` Type: `UpdateBoardRequest` - `name` (string, required, max length 200) - `description` (string): Replaces the description; left out, it is cleared. - `team_id` (string): Left out keeps the board's team; `""` moves it to the whole organization. - `visibility` (Visibility, one of `org`, `team`, `private`) ## Responses ### 200 — The board was changed. Content type: `application/json` Type: `IdEnvelope` - `success` (boolean) - `data` (object) - `id` (string) ### 400 — The body is not JSON (`INVALID_BODY`), `name` is missing or blank (`MISSING_NAME`), `name` is over 200 characters (`VALIDATION_FAILED`), `visibility` is unknown (`INVALID_VISIBILITY`), or a `team` board would have no team (`TEAM_REQUIRED`). Content type: `application/json` Type: `Error | ValidationFailed` - One of: - Error - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) - ValidationFailed - `success` (boolean, value `false`) - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `details` (array of object, required) - `field` (string) - `message` (string) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks `retro:board:write` (`FORBIDDEN`), or `team_id` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Sprint 42 retrospective", "description": "What went well and what to change after sprint 42" }' ``` --- Source: https://docs.evohub.io/retro/close-retro-board.md # Close a board `POST https://evohub.io/api/v1/retro/boards/{boardID}/close` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `closeRetroBoard`. Closes the board: it stays readable but its content can no longer change, and a running timer is stopped. Closing a closed board changes nothing. No request body. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Responses ### 200 — The board is closed. Content type: `application/json` Type: `BoardStatusEnvelope` - `success` (boolean) - `data` (object) - `id` (string) - `status` (string, one of `active`, `closed`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/close' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/reopen-retro-board.md # Reopen a board `POST https://evohub.io/api/v1/retro/boards/{boardID}/reopen` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `reopenRetroBoard`. Makes a closed board active again, so its content can change. No request body. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Responses ### 200 — The board is active. Content type: `application/json` Type: `BoardStatusEnvelope` - `success` (boolean) - `data` (object) - `id` (string) - `status` (string, one of `active`, `closed`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/reopen' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/list-retro-board-members.md # List who a board is shared with `GET https://evohub.io/api/v1/retro/boards/{boardID}/members` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `listRetroBoardMembers`. The people named on the board and the teams it was shared with, oldest first. Both see the board whatever its visibility. Requires the `retro:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Responses ### 200 — The people and teams. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (object) - `members` (array of BoardMember) - `board_id` (string) - `user_id` (string) - `added_by` (string): Who named them; left out when unknown. - `created_at` (string (date-time)) - `teams` (array of BoardTeam) - `board_id` (string) - `team_id` (string) - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/members' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/add-retro-board-member.md # Add a person to a board `POST https://evohub.io/api/v1/retro/boards/{boardID}/members` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `addRetroBoardMember`. Names a person on the board, so they see it whatever its visibility. Naming someone already named changes nothing. Being named only decides who sees the board; what they may do on it comes from their organization role. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Request body (required) Content type: `application/json` Type: `object` - `user_id` (string, required): The person's user ID. ## Responses ### 201 — The person is named on the board. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (object) - `user_id` (string) ### 400 — The body is not JSON (`INVALID_BODY`), or `user_id` is missing or blank (`MISSING_USER`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/members' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "user_id": "usr_9c4d2f7a1e6b3058" }' ``` --- Source: https://docs.evohub.io/retro/remove-retro-board-member.md # Remove a person from a board `DELETE https://evohub.io/api/v1/retro/boards/{boardID}/members/{userID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `removeRetroBoardMember`. Takes a person off the board. They still see it if its visibility or a team it was shared with lets them. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `userID` (string, required, example `usr_9c4d2f7a1e6b3058`): The person's user ID. ## Responses ### 204 — The person is no longer named on the board. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board is not one the caller can see, or the person is not named on it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/members/usr_9c4d2f7a1e6b3058' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/update-retro-share-mode.md # Set the share mode `PATCH https://evohub.io/api/v1/retro/boards/{boardID}/share` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `updateRetroShareMode`. Sets what the board's share link lets a guest do: | `mode` | A guest with the link | | --- | --- | | `disabled` | Cannot open the board. | | `read_only` | Can view the board. | | `participate` | Can view it and add cards, comments, reactions and votes, and edit or delete their own. | The share mode never limits members of the organization. To revoke links already handed out, rotate the token. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Request body (required) Content type: `application/json` Type: `object` - `mode` (ShareMode, required, one of `disabled`, `read_only`, `participate`) ## Responses ### 200 — The share mode was set. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (object) - `share_mode` (ShareMode, one of `disabled`, `read_only`, `participate`) ### 400 — The body is not JSON (`INVALID_BODY`), or `mode` is not `disabled`, `read_only` or `participate` (`INVALID_MODE`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/share' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "mode": "read_only" }' ``` --- Source: https://docs.evohub.io/retro/rotate-retro-share-token.md # Rotate the share link `POST https://evohub.io/api/v1/retro/boards/{boardID}/share/rotate` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `rotateRetroShareToken`. Issues a new share token for the board. Every link built from the old one stops working at once. The share mode is kept. No request body. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Responses ### 200 — The new token and link. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (object) - `share_token` (string) - `share_url` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/share/rotate' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/share-retro-board-with-team.md # Share a board with a team `POST https://evohub.io/api/v1/retro/boards/{boardID}/teams` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `shareRetroBoardWithTeam`. Gives the board to another team, so its members see it whatever its visibility. Sharing with a team that already has it changes nothing. The board's own team sees it through its visibility and cannot be added. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Request body (required) Content type: `application/json` Type: `object` - `team_id` (string, required): The team's ID. ## Responses ### 201 — The team now sees the board. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (object) - `team_id` (string) ### 400 — The body is not JSON (`INVALID_BODY`), `team_id` is missing or blank (`MISSING_TEAM`), or it is the board's own team (`OWN_TEAM`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/teams' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "team_id": "team_0d8b6f4a2c9e1735" }' ``` --- Source: https://docs.evohub.io/retro/unshare-retro-board-from-team.md # Stop sharing a board with a team `DELETE https://evohub.io/api/v1/retro/boards/{boardID}/teams/{teamID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `unshareRetroBoardFromTeam`. Takes the board away from a team it was shared with. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `teamID` (string, required, example `team_0d8b6f4a2c9e1735`): The team's ID. ## Responses ### 204 — The team no longer has the board. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board is not one the caller can see, or it is not shared with that team (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/teams/team_0d8b6f4a2c9e1735' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/update-retro-board-settings.md # Change a board's settings `PATCH https://evohub.io/api/v1/retro/boards/{boardID}/settings` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `updateRetroBoardSettings`. Turns voting and comments on or off, and sorts every column's cards by votes (highest first) for everyone on the board. Fields left out are kept. The answer is all three settings as they now stand. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Request body (required) Content type: `application/json` Type: `BoardSettings` - `voting_enabled` (boolean) - `comments_enabled` (boolean) - `sort_by_votes` (boolean) ## Responses ### 200 — The board's settings. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (BoardSettings) - `voting_enabled` (boolean) - `comments_enabled` (boolean) - `sort_by_votes` (boolean) ### 400 — The body is not JSON (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/settings' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "sort_by_votes": true, "voting_enabled": false }' ``` --- Source: https://docs.evohub.io/retro/control-retro-timer.md # Control the board's timer `POST https://evohub.io/api/v1/retro/boards/{boardID}/timer` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `controlRetroTimer`. Runs the board's shared countdown, which everyone on the board sees: | `action` | What it does | | --- | --- | | `start` | Starts a countdown of `minutes` (1–120), replacing any timer. | | `pause` | Pauses a running countdown that has time left. | | `resume` | Resumes a paused countdown. | | `add_minute` | Adds a minute to a running or paused countdown; one that reached zero restarts from now. | | `stop` | Clears the timer. | An action the timer is not in a state for answers **409** `TIMER_STATE`. The board must be active. The answer is the timer afterwards; count down against `server_time` rather than your own clock. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Request body (required) Content type: `application/json` Type: `object` - `action` (string, required, one of `start`, `pause`, `resume`, `add_minute`, `stop`) - `minutes` (integer, min 1, max 120): The countdown's length; required for `start`, ignored otherwise. ## Responses ### 200 — The timer. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (Timer) - `state` (string, one of `idle`, `running`, `paused`): A running timer whose end has passed stays `running` with `remaining_ms` 0 until it is stopped. - `ends_at` (string (date-time)): When a running countdown ends; left out otherwise. - `remaining_ms` (integer (int64)): Time left, in milliseconds. - `server_time` (string (date-time)): The server's clock when the timer was read. ### 400 — The body is not JSON (`INVALID_BODY`), `action` is unknown (`INVALID_ACTION`), or `start` has `minutes` outside 1–120 (`INVALID_MINUTES`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed (`BOARD_CLOSED`), or the timer cannot take this action now (`TIMER_STATE`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/timer' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "action": "start", "minutes": 5 }' ``` --- Source: https://docs.evohub.io/retro/create-retro-column.md # Add a column `POST https://evohub.io/api/v1/retro/boards/{boardID}/columns` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `createRetroColumn`. Adds a column to an active board. `name` is required; `emoji` and `color` are free text the console shows; `position` orders columns (lowest first) among those with the same `sort_order`. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Request body (required) Content type: `application/json` Type: `CreateColumnRequest` - `name` (string, required) - `emoji` (string) - `color` (string) - `position` (integer) ## Responses ### 201 — The column was added. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (Column) - `id` (string, example `col_1a2b3c4d5e6f7a80`) - `board_id` (string) - `name` (string) - `emoji` (string) - `color` (string) - `position` (integer) - `sort_order` (integer) - `created_at` (string (date-time)) - `cards` (array of Card): Only when a whole board is read. - `id` (string, example `crd_8d0c4b6e1a9f2357`) - `board_id` (string) - `column_id` (string) - `content` (string) - `created_at` (string (date-time)) - `reactions` (array of Reaction): Only when a whole board is read; left out when there are none. - `comments` (array of Comment): Only when a whole board is read; left out when there are none. - `vote_score` (integer): The net of agree (+1) and disagree (-1) votes. - `my_vote` (integer, one of `-1`, `0`, `1`): The caller's own vote on the card. ### 400 — The body is not JSON (`INVALID_BODY`), or `name` is missing or blank (`MISSING_NAME`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/columns' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "What went well", "color": "green", "emoji": "🎉", "position": 0 }' ``` --- Source: https://docs.evohub.io/retro/delete-retro-column.md # Delete a column `DELETE https://evohub.io/api/v1/retro/boards/{boardID}/columns/{columnID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `deleteRetroColumn`. Deletes a column from an active board, with all of its cards and their comments, reactions and votes. Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `columnID` (string, required, example `col_1a2b3c4d5e6f7a80`): The column's ID. ## Responses ### 204 — The column was deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/columns/col_1a2b3c4d5e6f7a80' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/update-retro-column.md # Change a column `PATCH https://evohub.io/api/v1/retro/boards/{boardID}/columns/{columnID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `updateRetroColumn`. Renames, restyles or reorders a column on an active board. Fields left out are kept. To reorder a board's columns, give each its `sort_order` (lowest first). Requires the `retro:board:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `columnID` (string, required, example `col_1a2b3c4d5e6f7a80`): The column's ID. ## Request body (required) Content type: `application/json` Type: `UpdateColumnRequest` - `name` (string) - `emoji` (string) - `color` (string) - `sort_order` (integer) ## Responses ### 200 — The column was changed. Content type: `application/json` Type: `IdEnvelope` - `success` (boolean) - `data` (object) - `id` (string) ### 400 — The body is not JSON (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/columns/col_1a2b3c4d5e6f7a80' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Went well", "sort_order": 1 }' ``` --- Source: https://docs.evohub.io/retro/merge-retro-cards.md # Merge cards `POST https://evohub.io/api/v1/retro/boards/{boardID}/cards/merge` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `mergeRetroCards`. Combines several cards on an active board into one. The primary card keeps its column and its text, followed by the other cards' text in the order they were written, one per line. Reactions move to the primary card (one per person and emoji); the other cards are deleted with their comments and votes. `card_ids` must hold at least two distinct cards, including `primary_card_id`, all on this board. Cards from other columns are merged into the primary card's column. The answer is the merged card; its `vote_score` and `my_vote` are not filled in, so read the board for them. Requires the `retro:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Request body (required) Content type: `application/json` Type: `MergeCardsRequest` - `primary_card_id` (string, required): The card the others are merged into. - `card_ids` (array of string, required, min items 2): Every card to merge, the primary card included. ## Responses ### 200 — The merged card. Content type: `application/json` Type: `CardEnvelope` - `success` (boolean) - `data` (Card) - `id` (string, example `crd_8d0c4b6e1a9f2357`) - `board_id` (string) - `column_id` (string) - `content` (string) - `created_at` (string (date-time)) - `reactions` (array of Reaction): Only when a whole board is read; left out when there are none. - `emoji` (string) - `count` (integer) - `reacted` (boolean): Whether the caller reacted with this emoji. - `comments` (array of Comment): Only when a whole board is read; left out when there are none. - `id` (string, example `cmt_4e6a8c0b2d1f3957`) - `card_id` (string) - `board_id` (string) - `content` (string) - `created_at` (string (date-time)) - `vote_score` (integer): The net of agree (+1) and disagree (-1) votes. - `my_vote` (integer, one of `-1`, `0`, `1`): The caller's own vote on the card. ### 400 — The body is not JSON (`INVALID_BODY`), `primary_card_id` is missing (`MISSING_PRIMARY_CARD_ID`), `card_ids` holds fewer than two distinct cards (`INSUFFICIENT_CARDS`), or it does not include the primary card (`PRIMARY_NOT_IN_LIST`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board is not one the caller can see (`NOT_FOUND`), or a card is not on it (`CARD_NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/cards/merge' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "card_ids": [ "crd_8d0c4b6e1a9f2357", "crd_2f4a6c8e0b1d3579" ], "primary_card_id": "crd_8d0c4b6e1a9f2357" }' ``` --- Source: https://docs.evohub.io/retro/delete-retro-card.md # Delete a card `DELETE https://evohub.io/api/v1/retro/boards/{boardID}/cards/{cardID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `deleteRetroCard`. Deletes a card with its comments, reactions and votes. The caller may delete their own cards; deleting someone else's needs `retro:board:write` as well. The board must be active. Requires the `retro:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `cardID` (string, required, example `crd_8d0c4b6e1a9f2357`): The card's ID. ## Responses ### 204 — The card was deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope, or the card is someone else's and the key lacks `retro:board:write` (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board is not one the caller can see (`NOT_FOUND`), or the card is not on it (`CARD_NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/cards/crd_8d0c4b6e1a9f2357' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/update-retro-card.md # Edit a card `PATCH https://evohub.io/api/v1/retro/boards/{boardID}/cards/{cardID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `updateRetroCard`. Replaces a card's text. The caller may edit their own cards; editing someone else's needs `retro:board:write` as well (**403** `FORBIDDEN` otherwise). The board must be active. Requires the `retro:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `cardID` (string, required, example `crd_8d0c4b6e1a9f2357`): The card's ID. ## Request body (required) Content type: `application/json` Type: `CardContentRequest` - `content` (string, required) ## Responses ### 200 — The card was changed. Content type: `application/json` Type: `IdEnvelope` - `success` (boolean) - `data` (object) - `id` (string) ### 400 — The body is not JSON (`INVALID_BODY`), or `content` is missing or blank (`MISSING_CONTENT`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope, or the card is someone else's and the key lacks `retro:board:write` (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board is not one the caller can see (`NOT_FOUND`), or the card is not on it (`CARD_NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/cards/crd_8d0c4b6e1a9f2357' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "content": "The new deploy pipeline saved us hours every week" }' ``` --- Source: https://docs.evohub.io/retro/create-retro-comment.md # Comment on a card `POST https://evohub.io/api/v1/retro/boards/{boardID}/cards/{cardID}/comments` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `createRetroComment`. Adds a comment to a card on an active board whose comments are on. Requires the `retro:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `cardID` (string, required, example `crd_8d0c4b6e1a9f2357`): The card's ID. ## Request body (required) Content type: `application/json` Type: `CardContentRequest` - `content` (string, required) ## Responses ### 201 — The comment was added. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (Comment) - `id` (string, example `cmt_4e6a8c0b2d1f3957`) - `card_id` (string) - `board_id` (string) - `content` (string) - `created_at` (string (date-time)) ### 400 — The body is not JSON (`INVALID_BODY`), or `content` is missing or blank (`MISSING_CONTENT`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks `retro:card:write` (`FORBIDDEN`), or comments are turned off on this board (`COMMENTS_DISABLED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board is not one the caller can see (`NOT_FOUND`), or the card is not on it (`CARD_NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/cards/crd_8d0c4b6e1a9f2357/comments' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "content": "Agreed, let'\''s document it" }' ``` --- Source: https://docs.evohub.io/retro/delete-retro-comment.md # Delete a comment `DELETE https://evohub.io/api/v1/retro/boards/{boardID}/cards/{cardID}/comments/{commentID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `deleteRetroComment`. Deletes a comment. The caller may delete their own comments; deleting someone else's needs `retro:board:write` as well. The board must be active. Requires the `retro:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `cardID` (string, required, example `crd_8d0c4b6e1a9f2357`): The card's ID. - `commentID` (string, required, example `cmt_4e6a8c0b2d1f3957`): The comment's ID. ## Responses ### 204 — The comment was deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope, or the comment is someone else's and the key lacks `retro:board:write` (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board is not one the caller can see (`NOT_FOUND`), or the comment is not on it (`COMMENT_NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/cards/crd_8d0c4b6e1a9f2357/comments/cmt_4e6a8c0b2d1f3957' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/toggle-retro-reaction.md # Add or remove a reaction `POST https://evohub.io/api/v1/retro/boards/{boardID}/cards/{cardID}/reactions` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `toggleRetroReaction`. Toggles the caller's reaction with one emoji on a card: adds it if the caller has not reacted with that emoji, removes it if they have. `added` says which happened. `reactions` is the card's reactions afterwards, most used first (`reacted` is not filled in here), or `null` when none are left. The board must be active. Requires the `retro:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `cardID` (string, required, example `crd_8d0c4b6e1a9f2357`): The card's ID. ## Request body (required) Content type: `application/json` Type: `object` - `emoji` (string, required): The emoji. ## Responses ### 200 — The reaction was toggled. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (object) - `added` (boolean): `true` if the reaction was added, `false` if it was removed. - `reactions` (array of Reaction | null) - `emoji` (string) - `count` (integer) - `reacted` (boolean): Whether the caller reacted with this emoji. ### 400 — The body is not JSON (`INVALID_BODY`), or `emoji` is missing or blank (`MISSING_EMOJI`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board is not one the caller can see (`NOT_FOUND`), or the card is not on it (`CARD_NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/cards/crd_8d0c4b6e1a9f2357/reactions' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "emoji": "🚀" }' ``` --- Source: https://docs.evohub.io/retro/vote-on-retro-card.md # Vote on a card `POST https://evohub.io/api/v1/retro/boards/{boardID}/cards/{cardID}/votes` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `voteOnRetroCard`. Agrees (`1`) or disagrees (`-1`) with a card. Each caller has one vote per card: voting the same way again removes it, voting the other way switches it. The answer is the card's net score and the caller's vote afterwards (`0` when removed). The board must be active and its voting on. Requires the `retro:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `cardID` (string, required, example `crd_8d0c4b6e1a9f2357`): The card's ID. ## Request body (required) Content type: `application/json` Type: `object` - `value` (integer, required, one of `1`, `-1`): `1` to agree, `-1` to disagree. ## Responses ### 200 — The vote was applied. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (object) - `vote_score` (integer): The card's net score across everyone. - `my_vote` (integer, one of `-1`, `0`, `1`): The caller's vote now. ### 400 — The body is not JSON (`INVALID_BODY`), or `value` is not `1` or `-1` (`INVALID_VALUE`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks `retro:card:write` (`FORBIDDEN`), or voting is turned off on this board (`VOTING_DISABLED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board is not one the caller can see (`NOT_FOUND`), or the card is not on it (`CARD_NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/cards/crd_8d0c4b6e1a9f2357/votes' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "value": 1 }' ``` --- Source: https://docs.evohub.io/retro/create-retro-card.md # Add a card `POST https://evohub.io/api/v1/retro/boards/{boardID}/columns/{columnID}/cards` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `createRetroCard`. Adds a card to a column of an active board. The card does not say who wrote it; the caller can later edit and delete it as its author. Requires the `retro:card:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `columnID` (string, required, example `col_1a2b3c4d5e6f7a80`): The column's ID. ## Request body (required) Content type: `application/json` Type: `CardContentRequest` - `content` (string, required) ## Responses ### 201 — The card was added. Content type: `application/json` Type: `CardEnvelope` - `success` (boolean) - `data` (Card) - `id` (string, example `crd_8d0c4b6e1a9f2357`) - `board_id` (string) - `column_id` (string) - `content` (string) - `created_at` (string (date-time)) - `reactions` (array of Reaction): Only when a whole board is read; left out when there are none. - `emoji` (string) - `count` (integer) - `reacted` (boolean): Whether the caller reacted with this emoji. - `comments` (array of Comment): Only when a whole board is read; left out when there are none. - `id` (string, example `cmt_4e6a8c0b2d1f3957`) - `card_id` (string) - `board_id` (string) - `content` (string) - `created_at` (string (date-time)) - `vote_score` (integer): The net of agree (+1) and disagree (-1) votes. - `my_vote` (integer, one of `-1`, `0`, `1`): The caller's own vote on the card. ### 400 — The body is not JSON (`INVALID_BODY`), or `content` is missing or blank (`MISSING_CONTENT`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board is not one the caller can see (`NOT_FOUND`), or the column is not on it (`COLUMN_NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/columns/col_1a2b3c4d5e6f7a80/cards' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "content": "The new deploy pipeline saved hours" }' ``` --- Source: https://docs.evohub.io/retro/list-my-action-items.md # List my open action items `GET https://evohub.io/api/v1/retro/action-items/mine` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `listMyActionItems`. The open action items owned by the caller on the boards they can see: overdue first, then by due date (no due date last), then oldest. `open` and `overdue` count every matching item, not only the ones returned. An item is the caller's when its `assignee_user_id` is the caller's user ID. With a personal key that is the key's person; an organization key owns no items, so it gets an empty list. Requires the `retro:action:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `limit` (integer, default `10`, min 1, max 50): How many items to return. Values above 50 are read as 50. - `team_id` (string, example `none`): Narrow to one team's boards plus the organization-wide ones, or `none` for only the organization-wide ones. It never widens what the caller can see. ## Responses ### 200 — The caller's open action items and their counts. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (MyActionItems) - `items` (array of object) - `id` (string) - `board_id` (string) - `board_name` (string) - `description` (string) - `status` (string, value `open`) - `due_at` (string (date-time) | null) - `created_at` (string (date-time)) - `open` (integer): All of the caller's open items. - `overdue` (integer): Those of them past their due date. ### 400 — `limit` is not a positive integer (`INVALID_LIMIT`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/retro/action-items/mine' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/list-retro-action-items.md # List a board's action items `GET https://evohub.io/api/v1/retro/boards/{boardID}/action-items` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `listRetroActionItems`. The board's action items, oldest first. Works on a closed board. `data` is `[]` when there are none. Requires the `retro:action:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Responses ### 200 — The action items. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (array of ActionItem) - `id` (string, example `act_5b2e8c1d9f4a7e30`) - `board_id` (string) - `description` (string) - `assignee` (string): A free-text owner name; `""` when none. - `assignee_user_id` (string | null): The organization member who owns the item. - `status` (string, one of `open`, `done`) - `created_at` (string (date-time)) - `updated_at` (string (date-time)): When the item last changed. - `completed_at` (string (date-time) | null): When the item was marked done; `null` while open, and for some items done before this was recorded. - `due_at` (string (date-time) | null) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/action-items' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/create-retro-action-item.md # Add an action item `POST https://evohub.io/api/v1/retro/boards/{boardID}/action-items` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `createRetroActionItem`. Adds an open action item to an active board. - `description` is required. - `assignee_user_id` makes a member of the organization the item's owner; anyone else answers **400** `ASSIGNEE_NOT_MEMBER`. `assignee` is a free-text name instead, for someone outside EvoHub. - `due_at` is RFC 3339, or left out or `null` for no due date. Requires the `retro:action:write` scope, and `retro:card:write` as well. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Request body (required) Content type: `application/json` Type: `CreateActionItemRequest` - `description` (string, required) - `assignee` (string): A free-text owner name. - `assignee_user_id` (string | null): An organization member who owns the item. - `due_at` (string (date-time) | null) ## Responses ### 201 — The action item was added. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (ActionItem) - `id` (string, example `act_5b2e8c1d9f4a7e30`) - `board_id` (string) - `description` (string) - `assignee` (string): A free-text owner name; `""` when none. - `assignee_user_id` (string | null): The organization member who owns the item. - `status` (string, one of `open`, `done`) - `created_at` (string (date-time)) - `updated_at` (string (date-time)): When the item last changed. - `completed_at` (string (date-time) | null): When the item was marked done; `null` while open, and for some items done before this was recorded. - `due_at` (string (date-time) | null) ### 400 — The body is not JSON or `due_at` is not RFC 3339 (`INVALID_BODY`), `description` is missing or blank (`MISSING_DESCRIPTION`), or `assignee_user_id` is not a member of the organization (`ASSIGNEE_NOT_MEMBER`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 503 — The owner's membership could not be checked right now (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/action-items' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "due_at": "2026-11-07T17:00:00Z", "description": "Add a staging smoke test before each release", "assignee_user_id": "usr_9c4d2f7a1e6b3058" }' ``` --- Source: https://docs.evohub.io/retro/delete-retro-action-item.md # Delete an action item `DELETE https://evohub.io/api/v1/retro/boards/{boardID}/action-items/{itemID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `deleteRetroActionItem`. Deletes an action item from an active board. Deleting one that is not on the board also answers 204. Requires the `retro:action:write` scope, and `retro:card:write` as well. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `itemID` (string, required, example `act_5b2e8c1d9f4a7e30`): The action item's ID. ## Responses ### 204 — The action item is gone. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/action-items/act_5b2e8c1d9f4a7e30' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/update-retro-action-item.md # Update an action item `PATCH https://evohub.io/api/v1/retro/boards/{boardID}/action-items/{itemID}` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `updateRetroActionItem`. Changes an action item's status, due date or owner; send at least one. - `status`: `open` or `done`. Marking an item done sets `completed_at`; reopening it clears it. - `due_at`: RFC 3339, or `null` to clear it. - `assignee_user_id`: a member of the organization, or `null` (or `""`) to clear it. Fields left out are kept. The board must be active. Requires the `retro:action:write` scope, and `retro:card:write` as well. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. - `itemID` (string, required, example `act_5b2e8c1d9f4a7e30`): The action item's ID. ## Request body (required) Content type: `application/json` Type: `UpdateActionItemRequest` - `status` (string, one of `open`, `done`) - `due_at` (string (date-time) | null): RFC 3339, or `null` to clear it. - `assignee_user_id` (string | null): An organization member, or `null` or `""` to clear it. ## Responses ### 200 — The action item as it now stands. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (ActionItemUpdate) - `id` (string) - `status` (string, one of `open`, `done`) - `completed_at` (string (date-time) | null) - `due_at` (string (date-time) | null) - `updated_at` (string (date-time)) - `assignee_user_id` (string | null) ### 400 — The body is not JSON or sends none of the fields (`INVALID_BODY`), `status` is not `open` or `done` (`INVALID_STATUS`), `due_at` is not RFC 3339 or `null` (`INVALID_DUE_AT`), `assignee_user_id` is not a string or `null` (`INVALID_ASSIGNEE_USER_ID`), or it is not a member of the organization (`ASSIGNEE_NOT_MEMBER`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board is not one the caller can see, or the action item is not on it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 409 — The board is closed and its content cannot change (`BOARD_CLOSED`). Reopen it first. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 503 — The owner's membership could not be checked right now (`MEMBER_CHECK_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/action-items/act_5b2e8c1d9f4a7e30' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "status": "done" }' ``` --- Source: https://docs.evohub.io/retro/export-retro-board.md # Export a board as PDF `GET https://evohub.io/api/v1/retro/boards/{boardID}/export` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `exportRetroBoard`. The board as a PDF file: its columns and cards and its action items. The file name, in `Content-Disposition`, is the board's name. Requires the `retro:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `boardID` (string, required, example `brd_3f9a1c7e2b8d4a60`): The board's ID. ## Responses ### 200 — The PDF. Headers: - `Content-Disposition` (string): `attachment; filename=".pdf"`. Content type: `application/pdf` Type: `string (binary)` ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 404 — The board does not exist, or the caller cannot see it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/retro/boards/brd_3f9a1c7e2b8d4a60/export' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/get-retro-analytics.md # Get retro analytics `GET https://evohub.io/api/v1/retro/analytics` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `getRetroAnalytics`. Activity, participation and action-item follow-through across every retro the caller can see (the same set the board list shows), over a range: headline totals, a time series, a breakdown by team, one row per retro held in the range (at most 500; `retros_truncated` says when there were more), the most agreed-with cards, and optionally the same totals for a comparison period. Without `from` and `to` the range is the last 30 UTC days, today included. A range of 48 hours or less is answered in hourly buckets, a longer one in daily buckets. Requires the `retro:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `from` (string, example `2026-10-01`): Start of the range: a date (`YYYY-MM-DD`, the start of that UTC day) or an RFC 3339 instant. Send it together with `to`. - `to` (string, example `2026-10-31`): End of the range: a date (`YYYY-MM-DD`, that UTC day included) or an RFC 3339 instant (exclusive). Must be after `from`, at most 366 days later. - `compare` (string, one of `previous`, `year`): Also total a comparison period: `previous` (the same length immediately before) or `year` (the same dates a year earlier). - `team_id` (string, example `none`): Narrow to one team's boards plus the organization-wide ones, or `none` for only the organization-wide ones. It never widens what the caller can see. ## Responses ### 200 — The analytics. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (RetroAnalytics) - `range` (object) - `from` (string (date-time)) - `to` (string (date-time)): Exclusive. - `granularity` (string, one of `hour`, `day`) - `tz` (string, value `UTC`) - `totals` (RetroTotals) - `retros` (integer) - `closed_retros` (integer) - `cards` (integer) - `votes` (integer) - `comments` (integer) - `participation` (Participation) - `action_items` (ActionStats) - `action_items_now` (object): The visible retros' action items as they stand now. - `open` (integer) - `overdue` (integer) - `with_due_at` (integer) - `open_age` (DurationStats) - `count` (integer) - `p50_seconds` (number | null) - `p90_seconds` (number | null) - `avg_seconds` (number | null) - `oldest_open_since` (string (date-time) | null) - `series` (array of object) - `bucket` (string (date-time)) - `retros` (integer) - `cards` (integer) - `votes` (integer) - `comments` (integer) - `active_people` (integer) - `action_items_created` (integer) - `action_items_completed` (integer) - `by_team` (array of object & RetroTotals): The totals per team; `team_id` `""` is the organization-wide retros. - `team_id` (string) - `retros` (integer) - `closed_retros` (integer) - `cards` (integer) - `votes` (integer) - `comments` (integer) - `participation` (Participation) - `participants` (integer): Distinct people (members and share-link guests) who wrote a card, voted or commented. - `guest_participants` (integer) - `participants_per_retro` (object): Over every retro, empty ones included. - `avg` (number | null) - `p50` (number | null) - `cards_per_participant` (number | null) - `votes_per_participant` (number | null) - `comments_per_participant` (number | null) - `rate` (number | null): Named members who took part divided by named members, over the retros naming at least two people; `null` when none does. - `rate_retros` (integer): How many retros `rate` is over. - `named_members` (integer) - `named_participants` (integer) - `action_items` (ActionStats) - `created` (integer): Action items created in the range. - `completed` (integer): Action items completed in the range. - `done_of_created` (integer): Of those created in the range, how many are done now. - `follow_through_rate` (number | null): `done_of_created` / `created`. - `done_without_date` (integer): Created in the range and done, but with no completion time recorded. - `time_to_complete` (DurationStats) - `retros` (array of object): One row per retro held in the range, at most 500. - `id` (string) - `name` (string) - `team_id` (string) - `status` (string, one of `active`, `closed`) - `created_at` (string (date-time)) - `participants` (integer) - `guest_participants` (integer) - `named_members` (integer) - `named_participants` (integer) - `participation_rate` (number | null) - `cards` (integer) - `votes` (integer) - `comments` (integer) - `action_items` (integer) - `action_items_done` (integer) - `retros_truncated` (boolean): `true` when more retros were held than `retros` lists. - `top_cards` (array of object): The most agreed-with cards of the range's retros. - `card_id` (string) - `board_id` (string) - `content` (string) - `agree` (integer) - `disagree` (integer) - `compare` (null | object & RetroTotals): The comparison period's totals; `null` without `compare`. - One of: - null - object & RetroTotals - `mode` (string, one of `previous`, `year`) - `from` (string (date-time)) - `to` (string (date-time)) - `retros` (integer) - `closed_retros` (integer) - `cards` (integer) - `votes` (integer) - `comments` (integer) - `participation` (Participation) - `participants` (integer): Distinct people (members and share-link guests) who wrote a card, voted or commented. - `guest_participants` (integer) - `participants_per_retro` (object): Over every retro, empty ones included. - `avg` (number | null) - `p50` (number | null) - `cards_per_participant` (number | null) - `votes_per_participant` (number | null) - `comments_per_participant` (number | null) - `rate` (number | null): Named members who took part divided by named members, over the retros naming at least two people; `null` when none does. - `rate_retros` (integer): How many retros `rate` is over. - `named_members` (integer) - `named_participants` (integer) - `action_items` (ActionStats) - `created` (integer): Action items created in the range. - `completed` (integer): Action items completed in the range. - `done_of_created` (integer): Of those created in the range, how many are done now. - `follow_through_rate` (number | null): `done_of_created` / `created`. - `done_without_date` (integer): Created in the range and done, but with no completion time recorded. - `time_to_complete` (DurationStats) ### 400 — A query parameter breaks a rule (`VALIDATION_FAILED`; `details` names each one): only one of `from` and `to`, a value that is neither a date nor RFC 3339, `to` not after `from`, a range over 366 days, or an unknown `compare`. Content type: `application/json` Type: `ValidationFailed` - `success` (boolean, value `false`) - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `details` (array of object, required) - `field` (string) - `message` (string) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/retro/analytics' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/get-retro-stats.md # Get board counts `GET https://evohub.io/api/v1/retro/stats` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `getRetroStats`. Counts of boards and action items across the whole organization, whatever team they belong to. Requires the `retro:board:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The counts. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (RetroStats) - `total_boards` (integer) - `active_boards` (integer) - `closed_boards` (integer) - `total_action_items` (integer) - `open_action_items` (integer) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/retro/stats' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/retro/list-retro-audit-logs.md # List audit log entries `GET https://evohub.io/api/v1/retro/audit-logs` Part of the [Retro API](https://docs.evohub.io/retro.md) reference · operationId `listRetroAuditLogs`. Changes made in Retro across the organization, newest first: boards created, renamed, moved, reshared, closed, reopened and deleted; people and teams added and removed; columns, cards and action items created, changed and deleted. Send both `resource_type` and `resource_id` to see one resource's history; one without the other is ignored. Requires the `retro:audit:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `resource_type` (string, one of `board`, `column`, `card`, `action_item`): The kind of resource, sent with `resource_id`. - `resource_id` (string, example `brd_3f9a1c7e2b8d4a60`): The resource's ID, sent with `resource_type`. - `limit` (integer, default `100`, min 1, max 500): How many entries to return. Values above 500 are read as 500; a value that is not a positive integer is ignored. ## Responses ### 200 — The entries; `[]` when there are none. Content type: `application/json` Type: `object` - `success` (boolean) - `data` (array of AuditLogEntry) - `id` (string, example `aud_1c7e9a3f5b2d8046`) - `org_id` (string) - `user_id` (string): Who made the change (for an organization key, the key's ID). - `resource_type` (string, one of `board`, `column`, `card`, `action_item`) - `resource_id` (string) - `action` (string, example `board.closed`): What happened, for example `board.created`, `board.updated`, `board.team_changed`, `board.visibility_changed`, `board.share_mode_updated`, `board.share_token_rotated`, `board.closed`, `board.reopened`, `board.deleted`, `board.member_set`, `board.member_removed`, `board.team_shared`, `board.team_unshared`, `column.created`, `column.updated`, `column.deleted`, `card.created`, `card.updated`, `card.deleted`, `card.merged`, `action_item.created`, `action_item.updated`, `action_item.deleted`. - `actor_type` (string, example `user`) - `details` (object): What changed; left out when there is nothing to add. - Other keys: any - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Retro itself; refusals before the request reaches Retro (401, 429) carry only `error`. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `board not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/retro/audit-logs' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status.md # Status API Version 1.0 · OpenAPI 3.1.0 The Status API: status pages, their components, incidents, scheduled maintenance, subscribers, custom domains, branding and visitor analytics. It is the API the EvoHub console itself uses. **Authentication.** Send an API key as `Authorization: Bearer evohub_…` or `X-API-Key: evohub_…`. A key works in one organization. See [API keys and scopes](/api-keys-and-scopes) and [Errors](/errors). The public page reads (tag *Public pages*) need no key at all. **Permissions.** Each operation names the permission it needs (`x-evohub-permission`); an API key needs it among its scopes. A key is always held to its scopes, even one made by an administrator. A refusal is **403 `FORBIDDEN`**, never 401. **Teams.** A page belongs to the whole organization (`team_id` is `""`) or to one team. A key sees the organization-wide pages; an organization key scoped to a team also sees that team's. A page the caller cannot see, and everything under it (components, incidents, maintenance, subscribers, domain, analytics), answers **404 `NOT_FOUND`**, exactly like one that does not exist. **Ids.** Pages, components, incidents, maintenance windows, their updates and subscribers have UUID ids. A path id that is not a UUID answers 404 `NOT_FOUND` before anything is read. **Responses.** Success is `{"data": …, "success": true}`. Errors are `{"error": {"code", "message", "request_id"}}`; this service's codes are `VALIDATION_ERROR` (400, the message names the field), `INVALID_BODY` (400, not JSON or an unknown field: bodies are decoded strictly), `NOT_FOUND`, and `INTERNAL_ERROR` (500). The hourly-views and cross-page analytics endpoints answer a bad range with `VALIDATION_FAILED` and `details`. Times are RFC 3339 in UTC. **Edit conflicts.** Pages and components carry a `version` that goes up on every edit. Send it back on a page update, a content-settings save or a component update and an edit made against an older version is refused with **409 `EDIT_CONFLICT`**; leave it out and the edit applies as before. **Incidents and maintenance are On-Call's.** On the console, incidents and maintenance windows are published and run from On-Call, which calls these same endpoints. Through this API you can run them on the page directly; set `managed_in: status_page` so the console shows them as the page's own. **Every write reaches the public page.** A successful write under a page drops the page's cached copy at the edge, so visitors see it on their next poll. **Not here.** The subscribe form, its confirm and unsubscribe links, and the visitor-analytics beacon are served to visitors by the status page itself (`x-route-check.not-public`). ## Servers - `https://evohub.io` ## Authentication - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Pages Status pages and their settings. - [GET /api/v1/pages](https://docs.evohub.io/status/list-pages.md): List pages - [POST /api/v1/pages](https://docs.evohub.io/status/create-page.md): Create a page - [GET /api/v1/pages/{pageID}](https://docs.evohub.io/status/get-page.md): Get a page - [PUT /api/v1/pages/{pageID}](https://docs.evohub.io/status/update-page.md): Update a page - [DELETE /api/v1/pages/{pageID}](https://docs.evohub.io/status/delete-page.md): Delete a page - [PATCH /api/v1/pages/{pageID}/content-settings](https://docs.evohub.io/status/update-page-content.md): Update content and SEO settings ## Page appearance Theme, header links, layout of groups, logos and favicon. - [PATCH /api/v1/pages/{pageID}/theme](https://docs.evohub.io/status/update-page-theme.md): Update the theme - [PATCH /api/v1/pages/{pageID}/links](https://docs.evohub.io/status/update-page-links.md): Update the header - [PATCH /api/v1/pages/{pageID}/product-groups](https://docs.evohub.io/status/update-page-layout.md): Set how groups are laid out - [POST /api/v1/pages/{pageID}/logo](https://docs.evohub.io/status/upload-page-logo.md): Upload the logo - [DELETE /api/v1/pages/{pageID}/logo](https://docs.evohub.io/status/remove-page-logo.md): Remove the logo - [POST /api/v1/pages/{pageID}/logo/dark](https://docs.evohub.io/status/upload-page-logo-dark.md): Upload the dark-mode logo - [DELETE /api/v1/pages/{pageID}/logo/dark](https://docs.evohub.io/status/remove-page-logo-dark.md): Remove the dark-mode logo - [POST /api/v1/pages/{pageID}/favicon](https://docs.evohub.io/status/upload-page-favicon.md): Upload the favicon - [DELETE /api/v1/pages/{pageID}/favicon](https://docs.evohub.io/status/remove-page-favicon.md): Remove the favicon - [GET /api/v1/pages/{pageID}/groups/logos](https://docs.evohub.io/status/list-group-logos.md): List group logos - [POST /api/v1/pages/{pageID}/groups/logo](https://docs.evohub.io/status/upload-group-logo.md): Upload a group's logo - [DELETE /api/v1/pages/{pageID}/groups/logo](https://docs.evohub.io/status/remove-group-logo.md): Remove a group's logo ## Components The services a page shows, and their status. - [GET /api/v1/pages/{pageID}/components](https://docs.evohub.io/status/list-components.md): List a page's components - [POST /api/v1/pages/{pageID}/components](https://docs.evohub.io/status/create-component.md): Add a component - [PUT /api/v1/pages/{pageID}/components/order](https://docs.evohub.io/status/reorder-components.md): Reorder components - [PUT /api/v1/pages/{pageID}/groups/rename](https://docs.evohub.io/status/rename-group.md): Rename a group - [GET /api/v1/components/{componentID}](https://docs.evohub.io/status/get-component.md): Get a component - [PUT /api/v1/components/{componentID}](https://docs.evohub.io/status/update-component.md): Update a component - [DELETE /api/v1/components/{componentID}](https://docs.evohub.io/status/delete-component.md): Delete a component - [PATCH /api/v1/components/{componentID}/status](https://docs.evohub.io/status/set-component-status.md): Set a component's status ## Incidents Incidents on a page and their timeline. - [GET /api/v1/pages/{pageID}/incidents](https://docs.evohub.io/status/list-incidents.md): List a page's incidents - [POST /api/v1/pages/{pageID}/incidents](https://docs.evohub.io/status/create-incident.md): Create an incident - [DELETE /api/v1/pages/{pageID}/incidents/{id}](https://docs.evohub.io/status/delete-incident.md): Delete an incident - [PATCH /api/v1/pages/{pageID}/incidents/{id}](https://docs.evohub.io/status/update-incident.md): Edit an incident - [POST /api/v1/pages/{pageID}/incidents/{id}/updates](https://docs.evohub.io/status/add-incident-update.md): Post an update - [DELETE /api/v1/pages/{pageID}/incidents/{id}/updates/{updateID}](https://docs.evohub.io/status/delete-incident-update.md): Delete an update - [PATCH /api/v1/pages/{pageID}/incidents/{id}/updates/{updateID}](https://docs.evohub.io/status/edit-incident-update.md): Edit an update's text - [PUT /api/v1/pages/{pageID}/incidents/{id}/postmortem](https://docs.evohub.io/status/set-incident-postmortem.md): Save the postmortem ## Maintenance Scheduled maintenance on a page and its timeline. - [GET /api/v1/pages/{pageID}/maintenances](https://docs.evohub.io/status/list-maintenances.md): List a page's maintenance - [POST /api/v1/pages/{pageID}/maintenances](https://docs.evohub.io/status/create-maintenance.md): Schedule maintenance - [DELETE /api/v1/pages/{pageID}/maintenances/{id}](https://docs.evohub.io/status/delete-maintenance.md): Delete maintenance - [PATCH /api/v1/pages/{pageID}/maintenances/{id}](https://docs.evohub.io/status/update-maintenance.md): Edit maintenance - [POST /api/v1/pages/{pageID}/maintenances/{id}/updates](https://docs.evohub.io/status/add-maintenance-update.md): Post a maintenance update - [DELETE /api/v1/pages/{pageID}/maintenances/{id}/updates/{updateID}](https://docs.evohub.io/status/delete-maintenance-update.md): Delete a maintenance update - [PATCH /api/v1/pages/{pageID}/maintenances/{id}/updates/{updateID}](https://docs.evohub.io/status/edit-maintenance-update.md): Edit a maintenance update's text ## Subscribers People who follow a page by email. - [GET /api/v1/pages/{pageID}/subscribers](https://docs.evohub.io/status/list-subscribers.md): List subscribers - [DELETE /api/v1/pages/{pageID}/subscribers/{id}](https://docs.evohub.io/status/delete-subscriber.md): Remove a subscriber - [PATCH /api/v1/pages/{pageID}/subscription-settings](https://docs.evohub.io/status/update-subscription-settings.md): Update subscription settings ## Custom domain Serve a page on your own subdomain. - [GET /api/v1/pages/{pageID}/domain](https://docs.evohub.io/status/get-domain.md): Get the custom domain - [POST /api/v1/pages/{pageID}/domain](https://docs.evohub.io/status/set-domain.md): Set the custom domain - [DELETE /api/v1/pages/{pageID}/domain](https://docs.evohub.io/status/delete-domain.md): Remove the custom domain ## Analytics Cookieless visitor analytics, incidents published and subscriber growth. - [GET /api/v1/pages/{pageID}/analytics](https://docs.evohub.io/status/get-page-analytics.md): Get a page's visitor analytics - [GET /api/v1/pages/{pageID}/analytics/hourly](https://docs.evohub.io/status/get-page-hourly-views.md): Get a page's views by the hour - [GET /api/v1/pages/{pageID}/analytics/export.csv](https://docs.evohub.io/status/export-page-analytics.md): Export analytics as CSV - [PATCH /api/v1/pages/{pageID}/analytics/settings](https://docs.evohub.io/status/update-analytics-settings.md): Update analytics settings - [GET /api/v1/pages/analytics](https://docs.evohub.io/status/get-status-analytics.md): Get analytics across pages ## Overview Every page's state in one read. - [GET /api/v1/pages/summary](https://docs.evohub.io/status/get-pages-summary.md): Summarize every page - [GET /api/v1/pages/overview](https://docs.evohub.io/status/get-pages-overview.md): Get every page's state ## Audit log Who changed what in Status. - [GET /api/v1/status-audit-logs](https://docs.evohub.io/status/list-status-audit-logs.md): List the Status audit log ## Public pages What visitors of a public page read. No API key. - [GET /api/v1/public/pages/{pageID}](https://docs.evohub.io/status/get-public-page.md): Read a public page - [GET /api/v1/public/pages/by-domain/{domain}](https://docs.evohub.io/status/get-public-page-by-domain.md): Read a public page by domain - [GET /api/v1/public/pages/{pageID}/feed.rss](https://docs.evohub.io/status/get-public-rss-feed.md): RSS feed - [GET /api/v1/public/pages/by-domain/{domain}/feed.rss](https://docs.evohub.io/status/get-public-rss-feed-by-domain.md): RSS feed by domain - [GET /api/v1/public/pages/{pageID}/feed.atom](https://docs.evohub.io/status/get-public-atom-feed.md): Atom feed - [GET /api/v1/public/pages/by-domain/{domain}/feed.atom](https://docs.evohub.io/status/get-public-atom-feed-by-domain.md): Atom feed by domain --- Source: https://docs.evohub.io/status/list-pages.md # List pages `GET https://evohub.io/api/v1/pages` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `listPages`. Every page the caller can see, newest first: the organization-wide ones, plus the team's own for an organization key scoped to a team. `[]` when there are none. **Permission (API-key scope):** `status:page:read`. ## Authorization Any one of: - `bearerAuth` (status:page:read) - `apiKeyHeader` (status:page:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `team_id` (string): Only this team's pages. A team the caller cannot see gives an empty list. ## Responses ### 200 — The pages. Content type: `application/json` Type: `object` - `data` (array of Page, required) - `id` (string (uuid), required) - `org_id` (string, required) - `team_id` (string, required): The team that manages the page; `""` for the whole organization. Never affects the public page. - `slug` (string, required): Unique in the organization. - `name` (string, required) - `logo_url` (string) - `logo_dark_url` (string): The logo the public page shows in dark mode; absent when the logo is used in both themes. - `favicon_url` (string) - `brand_color` (string): `#rrggbb`. - `custom_domain` (string) - `is_public` (boolean, required): Whether visitors can read it. - `theme` (string, required, one of `auto`, `light`, `dark`) - `site_theme` (SiteTheme, required) - `font` (string, one of ``, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Source Sans 3`, `IBM Plex Sans`, `Nunito Sans`, `Work Sans`, `Manrope`, `DM Sans`, `Plus Jakarta Sans`, `Figtree`, `Geist`, `Poppins`, `Merriweather`, `Source Serif 4`, `Lora`): `""` is the system font. - `radius` (string, one of `none`, `small`, `medium`, `large`, default `medium`) - `background` (string, one of `default`, `tinted`, `slate`, `warm`, default `default`) - `mode` (string, one of `system`, `light`, `dark`, default `system`): The same setting as the page's `theme` (`system` there is `auto`). - `subscriptions_enabled` (boolean) - `reply_to_email` (string): Where replies to subscriber emails go. - `page_description` (string) - `footer_text` (string) - `support_url` (string) - `support_label` (string) - `privacy_url` (string) - `terms_url` (string) - `ga_tag` (string): The older field for the Google Analytics id, kept in step with `analytics.ga4_measurement_id`. - `search_indexable` (boolean) - `history_window_days` (integer, one of `30`, `90`): How far back the public page shows past incidents. - `product_groups` (boolean): Groups fold into one line each on the public page. - `open_groups` (array of string): Groups that start open. - `product_pages` (boolean): Each group has a page of its own. - `header_links` (array of HeaderLink, max items 5) - `label` (string, required, min length 1, max length 40): One line of text. - `url` (string (uri), required, max length 2048): An `https://` URL. - `show_page_name` (boolean): `false` shows the logo alone. - `analytics` (AnalyticsSettings) - `enabled` (boolean): Whether visitor analytics are collected. - `retention_days` (integer, one of `30`, `90`, `180`, `365`, `730`): How long daily counts are kept. - `ga4_measurement_id` (string): A Google Analytics 4 id (`G-…`) the public page loads, or `""`. - `honor_privacy_signals` (boolean): Load Google Analytics only for visitors who sent neither `DNT: 1` nor `Sec-GPC: 1`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit of the page's settings. Send it back on an update to be refused (409 `EDIT_CONFLICT`) if the page changed since you read it. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/create-page.md # Create a page `POST https://evohub.io/api/v1/pages` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `createPage`. Creates a status page. It is public unless `is_public` is `false`. Add components, then point a custom domain at it. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `PageCreate` - `slug` (string, required): Unique in the organization. - `name` (string, required, max length 200) - `logo_url` (string | null): An http or https URL. - `logo_dark_url` (string | null): The dark-mode logo, an http or https URL. - `brand_color` (string | null): `#rrggbb`. Not checked here; `PATCH …/theme` checks it. - `is_public` (boolean, default `true`) - `theme` (string, one of `auto`, `light`, `dark`, default `auto`) - `team_id` (string | null): The key's own team (an organization key scoped to a team), or `""` for the whole organization. ## Responses ### 201 — The page. Content type: `application/json` Type: `object` - `data` (Page, required) - `id` (string (uuid), required) - `org_id` (string, required) - `team_id` (string, required): The team that manages the page; `""` for the whole organization. Never affects the public page. - `slug` (string, required): Unique in the organization. - `name` (string, required) - `logo_url` (string) - `logo_dark_url` (string): The logo the public page shows in dark mode; absent when the logo is used in both themes. - `favicon_url` (string) - `brand_color` (string): `#rrggbb`. - `custom_domain` (string) - `is_public` (boolean, required): Whether visitors can read it. - `theme` (string, required, one of `auto`, `light`, `dark`) - `site_theme` (SiteTheme, required) - `font` (string, one of ``, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Source Sans 3`, `IBM Plex Sans`, `Nunito Sans`, `Work Sans`, `Manrope`, `DM Sans`, `Plus Jakarta Sans`, `Figtree`, `Geist`, `Poppins`, `Merriweather`, `Source Serif 4`, `Lora`): `""` is the system font. - `radius` (string, one of `none`, `small`, `medium`, `large`, default `medium`) - `background` (string, one of `default`, `tinted`, `slate`, `warm`, default `default`) - `mode` (string, one of `system`, `light`, `dark`, default `system`): The same setting as the page's `theme` (`system` there is `auto`). - `subscriptions_enabled` (boolean) - `reply_to_email` (string): Where replies to subscriber emails go. - `page_description` (string) - `footer_text` (string) - `support_url` (string) - `support_label` (string) - `privacy_url` (string) - `terms_url` (string) - `ga_tag` (string): The older field for the Google Analytics id, kept in step with `analytics.ga4_measurement_id`. - `search_indexable` (boolean) - `history_window_days` (integer, one of `30`, `90`): How far back the public page shows past incidents. - `product_groups` (boolean): Groups fold into one line each on the public page. - `open_groups` (array of string): Groups that start open. - `product_pages` (boolean): Each group has a page of its own. - `header_links` (array of HeaderLink, max items 5) - `label` (string, required, min length 1, max length 40): One line of text. - `url` (string (uri), required, max length 2048): An `https://` URL. - `show_page_name` (boolean): `false` shows the logo alone. - `analytics` (AnalyticsSettings) - `enabled` (boolean): Whether visitor analytics are collected. - `retention_days` (integer, one of `30`, `90`, `180`, `365`, `730`): How long daily counts are kept. - `ga4_measurement_id` (string): A Google Analytics 4 id (`G-…`) the public page loads, or `""`. - `honor_privacy_signals` (boolean): Load Google Analytics only for visitors who sent neither `DNT: 1` nor `Sec-GPC: 1`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit of the page's settings. Send it back on an update to be refused (409 `EDIT_CONFLICT`) if the page changed since you read it. - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope (`FORBIDDEN`), or `team_id` is a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 409 — The organization already has a page with this slug (`SLUG_TAKEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/pages' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Acme Status", "slug": "acme", "theme": "auto", "team_id": "" }' ``` --- Source: https://docs.evohub.io/status/get-page.md # Get a page `GET https://evohub.io/api/v1/pages/{pageID}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getPage`. Returns one page. **Permission (API-key scope):** `status:page:read`. ## Authorization Any one of: - `bearerAuth` (status:page:read) - `apiKeyHeader` (status:page:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 200 — The page. Content type: `application/json` Type: `object` - `data` (Page, required) - `id` (string (uuid), required) - `org_id` (string, required) - `team_id` (string, required): The team that manages the page; `""` for the whole organization. Never affects the public page. - `slug` (string, required): Unique in the organization. - `name` (string, required) - `logo_url` (string) - `logo_dark_url` (string): The logo the public page shows in dark mode; absent when the logo is used in both themes. - `favicon_url` (string) - `brand_color` (string): `#rrggbb`. - `custom_domain` (string) - `is_public` (boolean, required): Whether visitors can read it. - `theme` (string, required, one of `auto`, `light`, `dark`) - `site_theme` (SiteTheme, required) - `font` (string, one of ``, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Source Sans 3`, `IBM Plex Sans`, `Nunito Sans`, `Work Sans`, `Manrope`, `DM Sans`, `Plus Jakarta Sans`, `Figtree`, `Geist`, `Poppins`, `Merriweather`, `Source Serif 4`, `Lora`): `""` is the system font. - `radius` (string, one of `none`, `small`, `medium`, `large`, default `medium`) - `background` (string, one of `default`, `tinted`, `slate`, `warm`, default `default`) - `mode` (string, one of `system`, `light`, `dark`, default `system`): The same setting as the page's `theme` (`system` there is `auto`). - `subscriptions_enabled` (boolean) - `reply_to_email` (string): Where replies to subscriber emails go. - `page_description` (string) - `footer_text` (string) - `support_url` (string) - `support_label` (string) - `privacy_url` (string) - `terms_url` (string) - `ga_tag` (string): The older field for the Google Analytics id, kept in step with `analytics.ga4_measurement_id`. - `search_indexable` (boolean) - `history_window_days` (integer, one of `30`, `90`): How far back the public page shows past incidents. - `product_groups` (boolean): Groups fold into one line each on the public page. - `open_groups` (array of string): Groups that start open. - `product_pages` (boolean): Each group has a page of its own. - `header_links` (array of HeaderLink, max items 5) - `label` (string, required, min length 1, max length 40): One line of text. - `url` (string (uri), required, max length 2048): An `https://` URL. - `show_page_name` (boolean): `false` shows the logo alone. - `analytics` (AnalyticsSettings) - `enabled` (boolean): Whether visitor analytics are collected. - `retention_days` (integer, one of `30`, `90`, `180`, `365`, `730`): How long daily counts are kept. - `ga4_measurement_id` (string): A Google Analytics 4 id (`G-…`) the public page loads, or `""`. - `honor_privacy_signals` (boolean): Load Google Analytics only for visitors who sent neither `DNT: 1` nor `Sec-GPC: 1`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit of the page's settings. Send it back on an update to be refused (409 `EDIT_CONFLICT`) if the page changed since you read it. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/update-page.md # Update a page `PUT https://evohub.io/api/v1/pages/{pageID}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `updatePage`. A partial update: a field left out keeps its stored value. `logo_url` and `brand_color` clear on `null`; `theme: ""` means `auto`. `slug` and `team_id` cannot change here: sent with a different value they are refused with `VALIDATION_ERROR`, sent with the stored value they are accepted. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `PageUpdate` - `name` (string, max length 200): Left out keeps the stored name; may not be empty or longer than 200 characters. - `logo_url` (string | null): Left out keeps the stored logo; `null` clears it. - `logo_dark_url` (string | null): Left out keeps the stored dark-mode logo; `null` or `""` clears it. - `brand_color` (string | null): Left out keeps the stored colour; `null` clears it. - `is_public` (boolean, default `true`): Left out keeps the stored visibility. - `theme` (string, one of `auto`, `light`, `dark`): Left out keeps the stored theme; `""` means `auto`. - `slug` (string): Cannot change. A different value is refused (`VALIDATION_ERROR`); the stored value is accepted. - `team_id` (string | null): Cannot change here. A different value is refused (`VALIDATION_ERROR`); the stored value is accepted. - `version` (integer | null): The page's `version` this edit was made against. Stale, the edit is refused (409 `EDIT_CONFLICT`); left out, it applies. ## Responses ### 200 — The page. Content type: `application/json` Type: `object` - `data` (Page, required) - `id` (string (uuid), required) - `org_id` (string, required) - `team_id` (string, required): The team that manages the page; `""` for the whole organization. Never affects the public page. - `slug` (string, required): Unique in the organization. - `name` (string, required) - `logo_url` (string) - `logo_dark_url` (string): The logo the public page shows in dark mode; absent when the logo is used in both themes. - `favicon_url` (string) - `brand_color` (string): `#rrggbb`. - `custom_domain` (string) - `is_public` (boolean, required): Whether visitors can read it. - `theme` (string, required, one of `auto`, `light`, `dark`) - `site_theme` (SiteTheme, required) - `font` (string, one of ``, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Source Sans 3`, `IBM Plex Sans`, `Nunito Sans`, `Work Sans`, `Manrope`, `DM Sans`, `Plus Jakarta Sans`, `Figtree`, `Geist`, `Poppins`, `Merriweather`, `Source Serif 4`, `Lora`): `""` is the system font. - `radius` (string, one of `none`, `small`, `medium`, `large`, default `medium`) - `background` (string, one of `default`, `tinted`, `slate`, `warm`, default `default`) - `mode` (string, one of `system`, `light`, `dark`, default `system`): The same setting as the page's `theme` (`system` there is `auto`). - `subscriptions_enabled` (boolean) - `reply_to_email` (string): Where replies to subscriber emails go. - `page_description` (string) - `footer_text` (string) - `support_url` (string) - `support_label` (string) - `privacy_url` (string) - `terms_url` (string) - `ga_tag` (string): The older field for the Google Analytics id, kept in step with `analytics.ga4_measurement_id`. - `search_indexable` (boolean) - `history_window_days` (integer, one of `30`, `90`): How far back the public page shows past incidents. - `product_groups` (boolean): Groups fold into one line each on the public page. - `open_groups` (array of string): Groups that start open. - `product_pages` (boolean): Each group has a page of its own. - `header_links` (array of HeaderLink, max items 5) - `label` (string, required, min length 1, max length 40): One line of text. - `url` (string (uri), required, max length 2048): An `https://` URL. - `show_page_name` (boolean): `false` shows the logo alone. - `analytics` (AnalyticsSettings) - `enabled` (boolean): Whether visitor analytics are collected. - `retention_days` (integer, one of `30`, `90`, `180`, `365`, `730`): How long daily counts are kept. - `ga4_measurement_id` (string): A Google Analytics 4 id (`G-…`) the public page loads, or `""`. - `honor_privacy_signals` (boolean): Load Google Analytics only for visitors who sent neither `DNT: 1` nor `Sec-GPC: 1`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit of the page's settings. Send it back on an update to be refused (409 `EDIT_CONFLICT`) if the page changed since you read it. - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 409 — `version` was sent and the page has changed since (`EDIT_CONFLICT`). Read it again and retry with its current `version`. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "Acme Status", "theme": "dark", "logo_url": "https://assets.evohub.io/logos/acme.png", "is_public": true }' ``` --- Source: https://docs.evohub.io/status/delete-page.md # Delete a page `DELETE https://evohub.io/api/v1/pages/{pageID}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `deletePage`. Deletes the page with its components, incidents, maintenance windows and subscribers, and releases its custom domain. Cannot be undone. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/update-page-content.md # Update content and SEO settings `PATCH https://evohub.io/api/v1/pages/{pageID}/content-settings` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `updatePageContent`. The page's description, footer, support/privacy/terms links, search indexing and history window, saved together. The text and link fields are written as sent (left out or blank clears them); `search_indexable` left out is `true`; `history_window_days` is 30, anything else is stored as 90. `show_uptime` left out keeps the stored value (on for every page until turned off). `ga_tag` left out is kept; `null` or `""` clears it (it also writes `analytics.ga4_measurement_id` when it is a GA4 id). **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `page_description` (string | null) - `footer_text` (string | null) - `support_url` (string | null): http, https or `mailto:`. - `support_label` (string | null) - `privacy_url` (string | null): http or https. - `terms_url` (string | null): http or https. - `ga_tag` (string | null) - `search_indexable` (boolean | null, default `true`) - `history_window_days` (integer | null, one of `30`, `90`, `null`, default `90`) - `show_uptime` (boolean | null): Show each component's uptime percentage on the public page. Off keeps the bars without it. Left out, unchanged. - `version` (integer | null): The page's `version` this save was made against. Stale, it is refused (409 `EDIT_CONFLICT`); left out, it applies. ## Responses ### 200 — The page. Content type: `application/json` Type: `object` - `data` (Page, required) - `id` (string (uuid), required) - `org_id` (string, required) - `team_id` (string, required): The team that manages the page; `""` for the whole organization. Never affects the public page. - `slug` (string, required): Unique in the organization. - `name` (string, required) - `logo_url` (string) - `logo_dark_url` (string): The logo the public page shows in dark mode; absent when the logo is used in both themes. - `favicon_url` (string) - `brand_color` (string): `#rrggbb`. - `custom_domain` (string) - `is_public` (boolean, required): Whether visitors can read it. - `theme` (string, required, one of `auto`, `light`, `dark`) - `site_theme` (SiteTheme, required) - `font` (string, one of ``, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Source Sans 3`, `IBM Plex Sans`, `Nunito Sans`, `Work Sans`, `Manrope`, `DM Sans`, `Plus Jakarta Sans`, `Figtree`, `Geist`, `Poppins`, `Merriweather`, `Source Serif 4`, `Lora`): `""` is the system font. - `radius` (string, one of `none`, `small`, `medium`, `large`, default `medium`) - `background` (string, one of `default`, `tinted`, `slate`, `warm`, default `default`) - `mode` (string, one of `system`, `light`, `dark`, default `system`): The same setting as the page's `theme` (`system` there is `auto`). - `subscriptions_enabled` (boolean) - `reply_to_email` (string): Where replies to subscriber emails go. - `page_description` (string) - `footer_text` (string) - `support_url` (string) - `support_label` (string) - `privacy_url` (string) - `terms_url` (string) - `ga_tag` (string): The older field for the Google Analytics id, kept in step with `analytics.ga4_measurement_id`. - `search_indexable` (boolean) - `history_window_days` (integer, one of `30`, `90`): How far back the public page shows past incidents. - `product_groups` (boolean): Groups fold into one line each on the public page. - `open_groups` (array of string): Groups that start open. - `product_pages` (boolean): Each group has a page of its own. - `header_links` (array of HeaderLink, max items 5) - `label` (string, required, min length 1, max length 40): One line of text. - `url` (string (uri), required, max length 2048): An `https://` URL. - `show_page_name` (boolean): `false` shows the logo alone. - `analytics` (AnalyticsSettings) - `enabled` (boolean): Whether visitor analytics are collected. - `retention_days` (integer, one of `30`, `90`, `180`, `365`, `730`): How long daily counts are kept. - `ga4_measurement_id` (string): A Google Analytics 4 id (`G-…`) the public page loads, or `""`. - `honor_privacy_signals` (boolean): Load Google Analytics only for visitors who sent neither `DNT: 1` nor `Sec-GPC: 1`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit of the page's settings. Send it back on an update to be refused (409 `EDIT_CONFLICT`) if the page changed since you read it. - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 409 — `version` was sent and the page has changed since (`EDIT_CONFLICT`). Read it again and retry with its current `version`. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/content-settings' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "support_url": "mailto:support@acme.com", "support_label": "Contact support", "page_description": "Live status of Acme services.", "search_indexable": true, "history_window_days": 90 }' ``` --- Source: https://docs.evohub.io/status/update-page-theme.md # Update the theme `PATCH https://evohub.io/api/v1/pages/{pageID}/theme` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `updatePageTheme`. The public page's font, corners, background and light/dark mode, and its brand colour. `site_theme` is merged over the current one (a key sent changes that choice; an unknown key is refused). `site_theme.mode` is also the page's `theme`. `brand_color` left out is kept; `null` or `""` clears it. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `site_theme` (SiteTheme) - `font` (string, one of ``, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Source Sans 3`, `IBM Plex Sans`, `Nunito Sans`, `Work Sans`, `Manrope`, `DM Sans`, `Plus Jakarta Sans`, `Figtree`, `Geist`, `Poppins`, `Merriweather`, `Source Serif 4`, `Lora`): `""` is the system font. - `radius` (string, one of `none`, `small`, `medium`, `large`, default `medium`) - `background` (string, one of `default`, `tinted`, `slate`, `warm`, default `default`) - `mode` (string, one of `system`, `light`, `dark`, default `system`): The same setting as the page's `theme` (`system` there is `auto`). - `brand_color` (string | null, pattern `^(#[0-9a-fA-F]{6})?$`): `#rrggbb`; `null` or `""` clears it. ## Responses ### 200 — The page. Content type: `application/json` Type: `object` - `data` (Page, required) - `id` (string (uuid), required) - `org_id` (string, required) - `team_id` (string, required): The team that manages the page; `""` for the whole organization. Never affects the public page. - `slug` (string, required): Unique in the organization. - `name` (string, required) - `logo_url` (string) - `logo_dark_url` (string): The logo the public page shows in dark mode; absent when the logo is used in both themes. - `favicon_url` (string) - `brand_color` (string): `#rrggbb`. - `custom_domain` (string) - `is_public` (boolean, required): Whether visitors can read it. - `theme` (string, required, one of `auto`, `light`, `dark`) - `site_theme` (SiteTheme, required) - `font` (string, one of ``, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Source Sans 3`, `IBM Plex Sans`, `Nunito Sans`, `Work Sans`, `Manrope`, `DM Sans`, `Plus Jakarta Sans`, `Figtree`, `Geist`, `Poppins`, `Merriweather`, `Source Serif 4`, `Lora`): `""` is the system font. - `radius` (string, one of `none`, `small`, `medium`, `large`, default `medium`) - `background` (string, one of `default`, `tinted`, `slate`, `warm`, default `default`) - `mode` (string, one of `system`, `light`, `dark`, default `system`): The same setting as the page's `theme` (`system` there is `auto`). - `subscriptions_enabled` (boolean) - `reply_to_email` (string): Where replies to subscriber emails go. - `page_description` (string) - `footer_text` (string) - `support_url` (string) - `support_label` (string) - `privacy_url` (string) - `terms_url` (string) - `ga_tag` (string): The older field for the Google Analytics id, kept in step with `analytics.ga4_measurement_id`. - `search_indexable` (boolean) - `history_window_days` (integer, one of `30`, `90`): How far back the public page shows past incidents. - `product_groups` (boolean): Groups fold into one line each on the public page. - `open_groups` (array of string): Groups that start open. - `product_pages` (boolean): Each group has a page of its own. - `header_links` (array of HeaderLink, max items 5) - `label` (string, required, min length 1, max length 40): One line of text. - `url` (string (uri), required, max length 2048): An `https://` URL. - `show_page_name` (boolean): `false` shows the logo alone. - `analytics` (AnalyticsSettings) - `enabled` (boolean): Whether visitor analytics are collected. - `retention_days` (integer, one of `30`, `90`, `180`, `365`, `730`): How long daily counts are kept. - `ga4_measurement_id` (string): A Google Analytics 4 id (`G-…`) the public page loads, or `""`. - `honor_privacy_signals` (boolean): Load Google Analytics only for visitors who sent neither `DNT: 1` nor `Sec-GPC: 1`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit of the page's settings. Send it back on an update to be refused (409 `EDIT_CONFLICT`) if the page changed since you read it. - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/theme' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "site_theme": { "font": "Inter", "mode": "dark", "radius": "large" }, "brand_color": "#4f46e5" }' ``` --- Source: https://docs.evohub.io/status/update-page-links.md # Update the header `PATCH https://evohub.io/api/v1/pages/{pageID}/links` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `updatePageLinks`. The links in the public page's header and whether the page name shows next to the logo. `header_links: []` removes every link; a field left out is unchanged. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `header_links` (array of HeaderLink, max items 5) - `label` (string, required, min length 1, max length 40): One line of text. - `url` (string (uri), required, max length 2048): An `https://` URL. - `show_page_name` (boolean) ## Responses ### 200 — The page. Content type: `application/json` Type: `object` - `data` (Page, required) - `id` (string (uuid), required) - `org_id` (string, required) - `team_id` (string, required): The team that manages the page; `""` for the whole organization. Never affects the public page. - `slug` (string, required): Unique in the organization. - `name` (string, required) - `logo_url` (string) - `logo_dark_url` (string): The logo the public page shows in dark mode; absent when the logo is used in both themes. - `favicon_url` (string) - `brand_color` (string): `#rrggbb`. - `custom_domain` (string) - `is_public` (boolean, required): Whether visitors can read it. - `theme` (string, required, one of `auto`, `light`, `dark`) - `site_theme` (SiteTheme, required) - `font` (string, one of ``, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Source Sans 3`, `IBM Plex Sans`, `Nunito Sans`, `Work Sans`, `Manrope`, `DM Sans`, `Plus Jakarta Sans`, `Figtree`, `Geist`, `Poppins`, `Merriweather`, `Source Serif 4`, `Lora`): `""` is the system font. - `radius` (string, one of `none`, `small`, `medium`, `large`, default `medium`) - `background` (string, one of `default`, `tinted`, `slate`, `warm`, default `default`) - `mode` (string, one of `system`, `light`, `dark`, default `system`): The same setting as the page's `theme` (`system` there is `auto`). - `subscriptions_enabled` (boolean) - `reply_to_email` (string): Where replies to subscriber emails go. - `page_description` (string) - `footer_text` (string) - `support_url` (string) - `support_label` (string) - `privacy_url` (string) - `terms_url` (string) - `ga_tag` (string): The older field for the Google Analytics id, kept in step with `analytics.ga4_measurement_id`. - `search_indexable` (boolean) - `history_window_days` (integer, one of `30`, `90`): How far back the public page shows past incidents. - `product_groups` (boolean): Groups fold into one line each on the public page. - `open_groups` (array of string): Groups that start open. - `product_pages` (boolean): Each group has a page of its own. - `header_links` (array of HeaderLink, max items 5) - `label` (string, required, min length 1, max length 40): One line of text. - `url` (string (uri), required, max length 2048): An `https://` URL. - `show_page_name` (boolean): `false` shows the logo alone. - `analytics` (AnalyticsSettings) - `enabled` (boolean): Whether visitor analytics are collected. - `retention_days` (integer, one of `30`, `90`, `180`, `365`, `730`): How long daily counts are kept. - `ga4_measurement_id` (string): A Google Analytics 4 id (`G-…`) the public page loads, or `""`. - `honor_privacy_signals` (boolean): Load Google Analytics only for visitors who sent neither `DNT: 1` nor `Sec-GPC: 1`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit of the page's settings. Send it back on an update to be refused (409 `EDIT_CONFLICT`) if the page changed since you read it. - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/links' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "header_links": [ { "url": "https://docs.acme.com", "label": "Docs" }, { "url": "https://acme.com/changelog", "label": "Changelog" } ], "show_page_name": false }' ``` --- Source: https://docs.evohub.io/status/update-page-layout.md # Set how groups are laid out `PATCH https://evohub.io/api/v1/pages/{pageID}/product-groups` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `updatePageLayout`. How the public page lays out component groups: one list (both off, the default), folded groups (`enabled`), or a page per group (`product_pages`, at most 3 groups). `open_groups` are the groups that start open (trimmed, blanks and repeats dropped, at most 100). A field left out is unchanged. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `enabled` (boolean) - `open_groups` (array of string) - `product_pages` (boolean) ## Responses ### 200 — The page. Content type: `application/json` Type: `object` - `data` (Page, required) - `id` (string (uuid), required) - `org_id` (string, required) - `team_id` (string, required): The team that manages the page; `""` for the whole organization. Never affects the public page. - `slug` (string, required): Unique in the organization. - `name` (string, required) - `logo_url` (string) - `logo_dark_url` (string): The logo the public page shows in dark mode; absent when the logo is used in both themes. - `favicon_url` (string) - `brand_color` (string): `#rrggbb`. - `custom_domain` (string) - `is_public` (boolean, required): Whether visitors can read it. - `theme` (string, required, one of `auto`, `light`, `dark`) - `site_theme` (SiteTheme, required) - `font` (string, one of ``, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Source Sans 3`, `IBM Plex Sans`, `Nunito Sans`, `Work Sans`, `Manrope`, `DM Sans`, `Plus Jakarta Sans`, `Figtree`, `Geist`, `Poppins`, `Merriweather`, `Source Serif 4`, `Lora`): `""` is the system font. - `radius` (string, one of `none`, `small`, `medium`, `large`, default `medium`) - `background` (string, one of `default`, `tinted`, `slate`, `warm`, default `default`) - `mode` (string, one of `system`, `light`, `dark`, default `system`): The same setting as the page's `theme` (`system` there is `auto`). - `subscriptions_enabled` (boolean) - `reply_to_email` (string): Where replies to subscriber emails go. - `page_description` (string) - `footer_text` (string) - `support_url` (string) - `support_label` (string) - `privacy_url` (string) - `terms_url` (string) - `ga_tag` (string): The older field for the Google Analytics id, kept in step with `analytics.ga4_measurement_id`. - `search_indexable` (boolean) - `history_window_days` (integer, one of `30`, `90`): How far back the public page shows past incidents. - `product_groups` (boolean): Groups fold into one line each on the public page. - `open_groups` (array of string): Groups that start open. - `product_pages` (boolean): Each group has a page of its own. - `header_links` (array of HeaderLink, max items 5) - `label` (string, required, min length 1, max length 40): One line of text. - `url` (string (uri), required, max length 2048): An `https://` URL. - `show_page_name` (boolean): `false` shows the logo alone. - `analytics` (AnalyticsSettings) - `enabled` (boolean): Whether visitor analytics are collected. - `retention_days` (integer, one of `30`, `90`, `180`, `365`, `730`): How long daily counts are kept. - `ga4_measurement_id` (string): A Google Analytics 4 id (`G-…`) the public page loads, or `""`. - `honor_privacy_signals` (boolean): Load Google Analytics only for visitors who sent neither `DNT: 1` nor `Sec-GPC: 1`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit of the page's settings. Send it back on an update to be refused (409 `EDIT_CONFLICT`) if the page changed since you read it. - `success` (boolean, required, value `true`) ### 400 — The body is not JSON, or has a field this endpoint does not take (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 409 — Turning on a page per group with more than 3 groups (`PRODUCT_LIMIT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/product-groups' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "enabled": true, "open_groups": [ "Platform" ] }' ``` --- Source: https://docs.evohub.io/status/upload-page-logo.md # Upload the logo `POST https://evohub.io/api/v1/pages/{pageID}/logo` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `uploadPageLogo`. Uploads the page's logo as multipart form field `file`: PNG, JPEG or WebP, at most 2 MB. The file's declared type and its content must both be one of those. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `multipart/form-data` Type: `object` - `file` (string (binary), required) ## Responses ### 200 — The logo's URL. Content type: `application/json` Type: `object` - `data` (object, required) - `logo_url` (string) - `success` (boolean, required, value `true`) ### 400 — The form or file is missing or unreadable (`INVALID_UPLOAD`, `MISSING_FILE`) or not a PNG, JPEG or WebP (`INVALID_CONTENT_TYPE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 413 — Larger than 2 MB (`FILE_TOO_LARGE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 502 — Storing the file failed (`UPLOAD_FAILED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 503 — Uploads are not configured (`UPLOAD_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/logo' \ -H 'Content-Type: multipart/form-data' \ -H 'Authorization: Bearer ' \ -d '{ "file": "string" }' ``` --- Source: https://docs.evohub.io/status/remove-page-logo.md # Remove the logo `DELETE https://evohub.io/api/v1/pages/{pageID}/logo` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `removePageLogo`. Removes the page's logo. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/logo' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/upload-page-logo-dark.md # Upload the dark-mode logo `POST https://evohub.io/api/v1/pages/{pageID}/logo/dark` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `uploadPageLogoDark`. Uploads the logo the public page shows in dark mode, as multipart form field `file`: PNG, JPEG or WebP, at most 2 MB. The file's declared type and its content must both be one of those. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `multipart/form-data` Type: `object` - `file` (string (binary), required) ## Responses ### 200 — The dark-mode logo's URL. Content type: `application/json` Type: `object` - `data` (object, required) - `logo_dark_url` (string) - `success` (boolean, required, value `true`) ### 400 — The form or file is missing or unreadable (`INVALID_UPLOAD`, `MISSING_FILE`) or not a PNG, JPEG or WebP (`INVALID_CONTENT_TYPE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 413 — Larger than 2 MB (`FILE_TOO_LARGE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 502 — Storing the file failed (`UPLOAD_FAILED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 503 — Uploads are not configured (`UPLOAD_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/logo/dark' \ -H 'Content-Type: multipart/form-data' \ -H 'Authorization: Bearer ' \ -d '{ "file": "string" }' ``` --- Source: https://docs.evohub.io/status/remove-page-logo-dark.md # Remove the dark-mode logo `DELETE https://evohub.io/api/v1/pages/{pageID}/logo/dark` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `removePageLogoDark`. Removes the dark-mode logo; the page then shows its logo in both themes. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/logo/dark' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/upload-page-favicon.md # Upload the favicon `POST https://evohub.io/api/v1/pages/{pageID}/favicon` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `uploadPageFavicon`. Uploads the public page's favicon as multipart form field `file`: PNG, JPEG or WebP, at most 2 MB. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `multipart/form-data` Type: `object` - `file` (string (binary), required) ## Responses ### 200 — The favicon's URL. Content type: `application/json` Type: `object` - `data` (object, required) - `favicon_url` (string) - `success` (boolean, required, value `true`) ### 400 — The form or file is missing or unreadable (`INVALID_UPLOAD`, `MISSING_FILE`) or not a PNG, JPEG or WebP (`INVALID_CONTENT_TYPE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 413 — Larger than 2 MB (`FILE_TOO_LARGE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 502 — Storing the file failed (`UPLOAD_FAILED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 503 — Uploads are not configured (`UPLOAD_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/favicon' \ -H 'Content-Type: multipart/form-data' \ -H 'Authorization: Bearer ' \ -d '{ "file": "string" }' ``` --- Source: https://docs.evohub.io/status/remove-page-favicon.md # Remove the favicon `DELETE https://evohub.io/api/v1/pages/{pageID}/favicon` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `removePageFavicon`. Removes the page's favicon. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/favicon' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/list-group-logos.md # List group logos `GET https://evohub.io/api/v1/pages/{pageID}/groups/logos` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `listGroupLogos`. Each group's logo. **Permission (API-key scope):** `status:page:read`. ## Authorization Any one of: - `bearerAuth` (status:page:read) - `apiKeyHeader` (status:page:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 200 — The logos. Content type: `application/json` Type: `object` - `data` (array of object, required) - `group` (string) - `logo_url` (string) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/groups/logos' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/upload-group-logo.md # Upload a group's logo `POST https://evohub.io/api/v1/pages/{pageID}/groups/logo` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `uploadGroupLogo`. Uploads one group's logo as multipart form field `file` (PNG, JPEG or WebP, at most 2 MB). The group must have a component on the page. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Query parameters - `group` (string, required, example `Platform`): The group's name, as on its components. ## Request body (required) Content type: `multipart/form-data` Type: `object` - `file` (string (binary), required) ## Responses ### 200 — The group's logo. Content type: `application/json` Type: `object` - `data` (object, required) - `group` (string) - `logo_url` (string) - `success` (boolean, required, value `true`) ### 400 — `group` is missing (`VALIDATION_ERROR`), or the form or file is not valid (`INVALID_UPLOAD`, `MISSING_FILE`, `INVALID_CONTENT_TYPE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page, or no such group on it (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 413 — Larger than 2 MB (`FILE_TOO_LARGE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 502 — Storing the file failed (`UPLOAD_FAILED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 503 — Uploads are not configured (`UPLOAD_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/groups/logo?group=Platform' \ -H 'Content-Type: multipart/form-data' \ -H 'Authorization: Bearer ' \ -d '{ "file": "string" }' ``` --- Source: https://docs.evohub.io/status/remove-group-logo.md # Remove a group's logo `DELETE https://evohub.io/api/v1/pages/{pageID}/groups/logo` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `removeGroupLogo`. Removes one group's logo. Removing a logo that is not there succeeds. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Query parameters - `group` (string, required, example `Platform`): The group's name, as on its components. ## Responses ### 204 — Removed. ### 400 — `group` is missing (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/groups/logo?group=Platform' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/list-components.md # List a page's components `GET https://evohub.io/api/v1/pages/{pageID}/components` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `listComponents`. The page's components in display order. A monitor-linked component's `current_status` is its monitor's live state (the stored one if Uptime is slow to answer), exactly as the public page shows it; `shown_status` and `shown_because` say when a live incident or maintenance shows it differently. **Permission (API-key scope):** `status:component:read`. ## Authorization Any one of: - `bearerAuth` (status:component:read) - `apiKeyHeader` (status:component:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 200 — The components. Content type: `application/json` Type: `object` - `data` (array of Component, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `name` (string, required) - `description` (string) - `source` (string, required, one of `manual`, `uptime_monitor`) - `monitor_id` (string): The Uptime monitor it mirrors (source `uptime_monitor`). - `current_status` (ComponentStatus, required, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `position` (integer, required) - `group` (string): The group (product) it appears under; absent when ungrouped. - `shown_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`): In the page's component list only, and only when a live incident or maintenance shows it differently from `current_status` on the public page. - `shown_because` (string): The live incident's or maintenance's title that makes visitors see `shown_status`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit (not on status changes or reorders). - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/components' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/create-component.md # Add a component `POST https://evohub.io/api/v1/pages/{pageID}/components` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `createComponent`. Adds a component, last unless `position` says otherwise. A component with `source: uptime_monitor` mirrors that Uptime monitor's live state; the monitor must be one of your organization's. **Permission (API-key scope):** `status:component:write`. ## Authorization Any one of: - `bearerAuth` (status:component:write) - `apiKeyHeader` (status:component:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `ComponentWrite` - `name` (string, required) - `description` (string | null) - `source` (string, one of `manual`, `uptime_monitor`, default `manual`) - `monitor_id` (string | null, pattern `^[A-Za-z0-9_-]{1,128}$`): Required for `uptime_monitor`: one of your organization's Uptime monitors. - `current_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`): On create defaults to `operational`; on update, left out keeps it. A monitor-linked component shows its monitor's live state instead. - `position` (integer | null): On create, left out puts it last; on update, left out keeps it. - `group` (string | null): Written as sent: on update, leaving it out or sending `""` ungroups it. - `version` (integer | null): Update only. The component's `version` this edit was made against. Stale, the edit is refused (409 `EDIT_CONFLICT`); left out, it applies. ## Responses ### 201 — The component. Content type: `application/json` Type: `object` - `data` (Component, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `name` (string, required) - `description` (string) - `source` (string, required, one of `manual`, `uptime_monitor`) - `monitor_id` (string): The Uptime monitor it mirrors (source `uptime_monitor`). - `current_status` (ComponentStatus, required, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `position` (integer, required) - `group` (string): The group (product) it appears under; absent when ungrouped. - `shown_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`): In the page's component list only, and only when a live incident or maintenance shows it differently from `current_status` on the public page. - `shown_because` (string): The live incident's or maintenance's title that makes visitors see `shown_status`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit (not on status changes or reorders). - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`), the monitor is not your organization's (`MONITOR_NOT_FOUND`), or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page (`PAGE_NOT_FOUND`, or `NOT_FOUND` for a page the caller cannot see). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 409 — A page per group is on and this would make more than 3 groups (`PRODUCT_LIMIT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/components' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "API", "group": "Platform", "source": "uptime_monitor", "monitor_id": "mon_7a8b9c", "description": "Public REST API" }' ``` --- Source: https://docs.evohub.io/status/reorder-components.md # Reorder components `PUT https://evohub.io/api/v1/pages/{pageID}/components/order` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `reorderComponents`. Sets the order of the page's components, in the console and on the public page. `component_ids` must name every component of the page exactly once. A group sits where its first component does. Answers with the reordered list. **Permission (API-key scope):** `status:component:write`. ## Authorization Any one of: - `bearerAuth` (status:component:write) - `apiKeyHeader` (status:component:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `component_ids` (array of string (uuid), required) ## Responses ### 200 — The components in their new order. Content type: `application/json` Type: `object` - `data` (array of Component, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `name` (string, required) - `description` (string) - `source` (string, required, one of `manual`, `uptime_monitor`) - `monitor_id` (string): The Uptime monitor it mirrors (source `uptime_monitor`). - `current_status` (ComponentStatus, required, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `position` (integer, required) - `group` (string): The group (product) it appears under; absent when ungrouped. - `shown_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`): In the page's component list only, and only when a live incident or maintenance shows it differently from `current_status` on the public page. - `shown_because` (string): The live incident's or maintenance's title that makes visitors see `shown_status`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit (not on status changes or reorders). - `success` (boolean, required, value `true`) ### 400 — The list does not name every component exactly once (`VALIDATION_ERROR`), or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/components/order' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "component_ids": [ "b8d2f4a6-3c5e-4d7f-9a1b-2c4e6a8b0d3f", "a7c1e3f5-2b4d-4c6e-8f0a-1b3d5f7a9c2e" ] }' ``` --- Source: https://docs.evohub.io/status/rename-group.md # Rename a group `PUT https://evohub.io/api/v1/pages/{pageID}/groups/rename` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `renameGroup`. Renames a group everywhere it is named: its components, `open_groups`, the subscribers who follow it and its logo, in one step. `to: null` (or `""`) takes the components out of any group. Answers with the page's components. **Permission (API-key scope):** `status:component:write`. ## Authorization Any one of: - `bearerAuth` (status:component:write) - `apiKeyHeader` (status:component:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `from` (string, required) - `to` (string | null, max length 200) ## Responses ### 200 — The page's components. Content type: `application/json` Type: `object` - `data` (array of Component, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `name` (string, required) - `description` (string) - `source` (string, required, one of `manual`, `uptime_monitor`) - `monitor_id` (string): The Uptime monitor it mirrors (source `uptime_monitor`). - `current_status` (ComponentStatus, required, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `position` (integer, required) - `group` (string): The group (product) it appears under; absent when ungrouped. - `shown_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`): In the page's component list only, and only when a live incident or maintenance shows it differently from `current_status` on the public page. - `shown_because` (string): The live incident's or maintenance's title that makes visitors see `shown_status`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit (not on status changes or reorders). - `success` (boolean, required, value `true`) ### 400 — `from` is missing or `to` is longer than 200 characters (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 409 — A page per group is on and this would make more than 3 groups (`PRODUCT_LIMIT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/groups/rename' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "to": "Core platform", "from": "Platform" }' ``` --- Source: https://docs.evohub.io/status/get-component.md # Get a component `GET https://evohub.io/api/v1/components/{componentID}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getComponent`. Returns one component with its stored `current_status` (the page's component list shows a monitor-linked component's live state instead). **Permission (API-key scope):** `status:component:read`. ## Authorization Any one of: - `bearerAuth` (status:component:read) - `apiKeyHeader` (status:component:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `componentID` (string (uuid), required, example `a7c1e3f5-2b4d-4c6e-8f0a-1b3d5f7a9c2e`): The component's id (a UUID). ## Responses ### 200 — The component. Content type: `application/json` Type: `object` - `data` (Component, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `name` (string, required) - `description` (string) - `source` (string, required, one of `manual`, `uptime_monitor`) - `monitor_id` (string): The Uptime monitor it mirrors (source `uptime_monitor`). - `current_status` (ComponentStatus, required, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `position` (integer, required) - `group` (string): The group (product) it appears under; absent when ungrouped. - `shown_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`): In the page's component list only, and only when a live incident or maintenance shows it differently from `current_status` on the public page. - `shown_because` (string): The live incident's or maintenance's title that makes visitors see `shown_status`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit (not on status changes or reorders). - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/components/a7c1e3f5-2b4d-4c6e-8f0a-1b3d5f7a9c2e' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/update-component.md # Update a component `PUT https://evohub.io/api/v1/components/{componentID}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `updateComponent`. Updates a component. `current_status` and `position` left out are kept; `description` and `group` are written as sent (left out clears them). **Permission (API-key scope):** `status:component:write`. ## Authorization Any one of: - `bearerAuth` (status:component:write) - `apiKeyHeader` (status:component:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `componentID` (string (uuid), required, example `a7c1e3f5-2b4d-4c6e-8f0a-1b3d5f7a9c2e`): The component's id (a UUID). ## Request body (required) Content type: `application/json` Type: `ComponentWrite` - `name` (string, required) - `description` (string | null) - `source` (string, one of `manual`, `uptime_monitor`, default `manual`) - `monitor_id` (string | null, pattern `^[A-Za-z0-9_-]{1,128}$`): Required for `uptime_monitor`: one of your organization's Uptime monitors. - `current_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`): On create defaults to `operational`; on update, left out keeps it. A monitor-linked component shows its monitor's live state instead. - `position` (integer | null): On create, left out puts it last; on update, left out keeps it. - `group` (string | null): Written as sent: on update, leaving it out or sending `""` ungroups it. - `version` (integer | null): Update only. The component's `version` this edit was made against. Stale, the edit is refused (409 `EDIT_CONFLICT`); left out, it applies. ## Responses ### 200 — The component. Content type: `application/json` Type: `object` - `data` (Component, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `name` (string, required) - `description` (string) - `source` (string, required, one of `manual`, `uptime_monitor`) - `monitor_id` (string): The Uptime monitor it mirrors (source `uptime_monitor`). - `current_status` (ComponentStatus, required, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `position` (integer, required) - `group` (string): The group (product) it appears under; absent when ungrouped. - `shown_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`): In the page's component list only, and only when a live incident or maintenance shows it differently from `current_status` on the public page. - `shown_because` (string): The live incident's or maintenance's title that makes visitors see `shown_status`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit (not on status changes or reorders). - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`), the monitor is not your organization's (`MONITOR_NOT_FOUND`), or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 409 — A page per group is on and this would make more than 3 groups (`PRODUCT_LIMIT`), or `version` was sent and the component has changed since (`EDIT_CONFLICT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/components/a7c1e3f5-2b4d-4c6e-8f0a-1b3d5f7a9c2e' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "API", "group": "Platform", "source": "uptime_monitor", "monitor_id": "mon_7a8b9c", "description": "Public REST API (v2)" }' ``` --- Source: https://docs.evohub.io/status/delete-component.md # Delete a component `DELETE https://evohub.io/api/v1/components/{componentID}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `deleteComponent`. Deletes a component. **Permission (API-key scope):** `status:component:write`. ## Authorization Any one of: - `bearerAuth` (status:component:write) - `apiKeyHeader` (status:component:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `componentID` (string (uuid), required, example `a7c1e3f5-2b4d-4c6e-8f0a-1b3d5f7a9c2e`): The component's id (a UUID). ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/components/a7c1e3f5-2b4d-4c6e-8f0a-1b3d5f7a9c2e' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/set-component-status.md # Set a component's status `PATCH https://evohub.io/api/v1/components/{componentID}/status` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `setComponentStatus`. Sets a component's status by hand. A monitor-linked component keeps showing its monitor's live state on the page. **Permission (API-key scope):** `status:component:write`. ## Authorization Any one of: - `bearerAuth` (status:component:write) - `apiKeyHeader` (status:component:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `componentID` (string (uuid), required, example `a7c1e3f5-2b4d-4c6e-8f0a-1b3d5f7a9c2e`): The component's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `current_status` (ComponentStatus, required, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) ## Responses ### 200 — The component. Content type: `application/json` Type: `object` - `data` (Component, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `name` (string, required) - `description` (string) - `source` (string, required, one of `manual`, `uptime_monitor`) - `monitor_id` (string): The Uptime monitor it mirrors (source `uptime_monitor`). - `current_status` (ComponentStatus, required, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `position` (integer, required) - `group` (string): The group (product) it appears under; absent when ungrouped. - `shown_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`): In the page's component list only, and only when a live incident or maintenance shows it differently from `current_status` on the public page. - `shown_because` (string): The live incident's or maintenance's title that makes visitors see `shown_status`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit (not on status changes or reorders). - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/components/a7c1e3f5-2b4d-4c6e-8f0a-1b3d5f7a9c2e/status' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "current_status": "degraded" }' ``` --- Source: https://docs.evohub.io/status/list-incidents.md # List a page's incidents `GET https://evohub.io/api/v1/pages/{pageID}/incidents` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `listIncidents`. Every incident of the page, with its affected components and timeline. Not paged. **Permission (API-key scope):** `status:incident:read`. ## Authorization Any one of: - `bearerAuth` (status:incident:read) - `apiKeyHeader` (status:incident:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 200 — The incidents. Content type: `application/json` Type: `object` - `data` (array of Incident, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `title` (string, required) - `impact` (IncidentImpact, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `started_at` (string (date-time), required) - `resolved_at` (string (date-time)) - `created_at` (string (date-time), required): When the page got it. - `updated_at` (string (date-time), required) - `postmortem_body` (string): Markdown; absent when none is written. - `postmortem_published_at` (string (date-time)): Set while the postmortem shows on the public page. - `backfilled` (boolean, required): Written after the fact with its own past times; it mailed no subscriber. - `managed_in` (ManagedIn, required, one of `oncall`, `status_page`) - `affected_component_ids` (array of string (uuid), required) - `updates` (array of IncidentUpdate, required): The timeline. - `id` (string (uuid)) - `incident_id` (string (uuid)) - `status` (IncidentStatus, one of `investigating`, `identified`, `monitoring`, `resolved`) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/incidents' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/create-incident.md # Create an incident `POST https://evohub.io/api/v1/pages/{pageID}/incidents` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `createIncident`. Opens an incident on the page. With `body` it gets a first timeline entry, and confirmed subscribers who follow an affected component (or the whole page) are mailed. To record one that is already over, send `started_at` and `resolved_at` (and `resolution_body`): it is marked `backfilled`, lands on the days it happened and mails nobody. **Permission (API-key scope):** `status:incident:write`. ## Authorization Any one of: - `bearerAuth` (status:incident:write) - `apiKeyHeader` (status:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `IncidentCreate` - `title` (string, required) - `impact` (IncidentImpact, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, one of `investigating`, `identified`, `monitoring`, `resolved`) - `affected_component_ids` (array of string (uuid)): Components of this page; an id that is not one of the page's components is skipped. - `body` (string | null): The first timeline entry. Mailed to subscribers unless the incident is backfilled. - `started_at` (string (date-time) | null): Records an incident that already began (at most two minutes in the future). Makes it `backfilled`. - `resolved_at` (string (date-time) | null): With `started_at`: when it ended. Sets the status to `resolved`. - `resolution_body` (string): What the resolved entry of a backfilled incident says. - `managed_in` (ManagedIn, one of `oncall`, `status_page`) ## Responses ### 201 — The incident. Content type: `application/json` Type: `object` - `data` (Incident, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `title` (string, required) - `impact` (IncidentImpact, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `started_at` (string (date-time), required) - `resolved_at` (string (date-time)) - `created_at` (string (date-time), required): When the page got it. - `updated_at` (string (date-time), required) - `postmortem_body` (string): Markdown; absent when none is written. - `postmortem_published_at` (string (date-time)): Set while the postmortem shows on the public page. - `backfilled` (boolean, required): Written after the fact with its own past times; it mailed no subscriber. - `managed_in` (ManagedIn, required, one of `oncall`, `status_page`) - `affected_component_ids` (array of string (uuid), required) - `updates` (array of IncidentUpdate, required): The timeline. - `id` (string (uuid)) - `incident_id` (string (uuid)) - `status` (IncidentStatus, one of `investigating`, `identified`, `monitoring`, `resolved`) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page (`PAGE_NOT_FOUND`, or `NOT_FOUND` for a page the caller cannot see). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/incidents' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "We are looking into elevated error rates on the API.", "title": "Elevated API error rates", "impact": "major", "status": "investigating", "managed_in": "status_page", "affected_component_ids": [ "a7c1e3f5-2b4d-4c6e-8f0a-1b3d5f7a9c2e" ] }' ``` --- Source: https://docs.evohub.io/status/delete-incident.md # Delete an incident `DELETE https://evohub.io/api/v1/pages/{pageID}/incidents/{id}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `deleteIncident`. Deletes the incident and its timeline. **Permission (API-key scope):** `status:incident:write`. ## Authorization Any one of: - `bearerAuth` (status:incident:write) - `apiKeyHeader` (status:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page or incident, or one on another page (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/incidents/00000000-0000-0000-0000-000000000000' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/update-incident.md # Edit an incident `PATCH https://evohub.io/api/v1/pages/{pageID}/incidents/{id}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `updateIncident`. Edits the title, impact or status; a field left out keeps its value. This adds no timeline entry and mails nobody: post an update for that. **Permission (API-key scope):** `status:incident:write`. ## Authorization Any one of: - `bearerAuth` (status:incident:write) - `apiKeyHeader` (status:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). ## Request body (required) Content type: `application/json` Type: `IncidentPatch` A field left out keeps its value. - `title` (string) - `impact` (IncidentImpact, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, one of `investigating`, `identified`, `monitoring`, `resolved`) ## Responses ### 200 — The incident. Content type: `application/json` Type: `object` - `data` (Incident, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `title` (string, required) - `impact` (IncidentImpact, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `started_at` (string (date-time), required) - `resolved_at` (string (date-time)) - `created_at` (string (date-time), required): When the page got it. - `updated_at` (string (date-time), required) - `postmortem_body` (string): Markdown; absent when none is written. - `postmortem_published_at` (string (date-time)): Set while the postmortem shows on the public page. - `backfilled` (boolean, required): Written after the fact with its own past times; it mailed no subscriber. - `managed_in` (ManagedIn, required, one of `oncall`, `status_page`) - `affected_component_ids` (array of string (uuid), required) - `updates` (array of IncidentUpdate, required): The timeline. - `id` (string (uuid)) - `incident_id` (string (uuid)) - `status` (IncidentStatus, one of `investigating`, `identified`, `monitoring`, `resolved`) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page or incident, or one on another page (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/incidents/00000000-0000-0000-0000-000000000000' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "impact": "minor" }' ``` --- Source: https://docs.evohub.io/status/add-incident-update.md # Post an update `POST https://evohub.io/api/v1/pages/{pageID}/incidents/{id}/updates` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `addIncidentUpdate`. Adds a timeline entry and moves the incident to its `status`; `resolved` sets `resolved_at`. Confirmed subscribers who follow an affected component (or the whole page) are mailed. **Permission (API-key scope):** `status:incident:write`. ## Authorization Any one of: - `bearerAuth` (status:incident:write) - `apiKeyHeader` (status:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `body` (string, required, min length 1) ## Responses ### 201 — The incident. Content type: `application/json` Type: `object` - `data` (Incident, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `title` (string, required) - `impact` (IncidentImpact, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `started_at` (string (date-time), required) - `resolved_at` (string (date-time)) - `created_at` (string (date-time), required): When the page got it. - `updated_at` (string (date-time), required) - `postmortem_body` (string): Markdown; absent when none is written. - `postmortem_published_at` (string (date-time)): Set while the postmortem shows on the public page. - `backfilled` (boolean, required): Written after the fact with its own past times; it mailed no subscriber. - `managed_in` (ManagedIn, required, one of `oncall`, `status_page`) - `affected_component_ids` (array of string (uuid), required) - `updates` (array of IncidentUpdate, required): The timeline. - `id` (string (uuid)) - `incident_id` (string (uuid)) - `status` (IncidentStatus, one of `investigating`, `identified`, `monitoring`, `resolved`) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page or incident, or one on another page (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/incidents/00000000-0000-0000-0000-000000000000/updates' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "A fix is deployed; we are watching error rates.", "status": "monitoring" }' ``` --- Source: https://docs.evohub.io/status/delete-incident-update.md # Delete an update `DELETE https://evohub.io/api/v1/pages/{pageID}/incidents/{id}/updates/{updateID}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `deleteIncidentUpdate`. Removes one timeline entry. **Permission (API-key scope):** `status:incident:write`. ## Authorization Any one of: - `bearerAuth` (status:incident:write) - `apiKeyHeader` (status:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). - `updateID` (string (uuid), required, example `d3f5b7c9-5e7a-4b9c-9d1e-4f6b8d0a2a51`): The timeline entry's id (a UUID). ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page or incident, or one on another page (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/incidents/00000000-0000-0000-0000-000000000000/updates/d3f5b7c9-5e7a-4b9c-9d1e-4f6b8d0a2a51' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/edit-incident-update.md # Edit an update's text `PATCH https://evohub.io/api/v1/pages/{pageID}/incidents/{id}/updates/{updateID}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `editIncidentUpdate`. Changes one timeline entry's text. The incident's status is unchanged and nobody is mailed. **Permission (API-key scope):** `status:incident:write`. ## Authorization Any one of: - `bearerAuth` (status:incident:write) - `apiKeyHeader` (status:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). - `updateID` (string (uuid), required, example `d3f5b7c9-5e7a-4b9c-9d1e-4f6b8d0a2a51`): The timeline entry's id (a UUID). ## Request body (required) Content type: `application/json` Type: `TimelineEntryEdit` - `body` (string, required, min length 1) ## Responses ### 200 — The incident. Content type: `application/json` Type: `object` - `data` (Incident, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `title` (string, required) - `impact` (IncidentImpact, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `started_at` (string (date-time), required) - `resolved_at` (string (date-time)) - `created_at` (string (date-time), required): When the page got it. - `updated_at` (string (date-time), required) - `postmortem_body` (string): Markdown; absent when none is written. - `postmortem_published_at` (string (date-time)): Set while the postmortem shows on the public page. - `backfilled` (boolean, required): Written after the fact with its own past times; it mailed no subscriber. - `managed_in` (ManagedIn, required, one of `oncall`, `status_page`) - `affected_component_ids` (array of string (uuid), required) - `updates` (array of IncidentUpdate, required): The timeline. - `id` (string (uuid)) - `incident_id` (string (uuid)) - `status` (IncidentStatus, one of `investigating`, `identified`, `monitoring`, `resolved`) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page or incident, or one on another page (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/incidents/00000000-0000-0000-0000-000000000000/updates/d3f5b7c9-5e7a-4b9c-9d1e-4f6b8d0a2a51' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "We are looking into elevated error rates on the public API." }' ``` --- Source: https://docs.evohub.io/status/set-incident-postmortem.md # Save the postmortem `PUT https://evohub.io/api/v1/pages/{pageID}/incidents/{id}/postmortem` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `setIncidentPostmortem`. Saves the postmortem (Markdown) and, with `published: true`, shows it on the public page. `published: false` saves it as a draft and takes it off the page. Only a resolved incident's postmortem can be published. **Permission (API-key scope):** `status:incident:write`. ## Authorization Any one of: - `bearerAuth` (status:incident:write) - `apiKeyHeader` (status:incident:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). ## Request body (required) Content type: `application/json` Type: `PostmortemWrite` - `body` (string): Markdown. Required (non-empty) to publish. - `published` (boolean, default `false`): Show it on the public page. Only a resolved incident's postmortem can be published. ## Responses ### 200 — The incident. Content type: `application/json` Type: `object` - `data` (Incident, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `title` (string, required) - `impact` (IncidentImpact, required, one of `none`, `minor`, `major`, `critical`) - `status` (IncidentStatus, required, one of `investigating`, `identified`, `monitoring`, `resolved`) - `started_at` (string (date-time), required) - `resolved_at` (string (date-time)) - `created_at` (string (date-time), required): When the page got it. - `updated_at` (string (date-time), required) - `postmortem_body` (string): Markdown; absent when none is written. - `postmortem_published_at` (string (date-time)): Set while the postmortem shows on the public page. - `backfilled` (boolean, required): Written after the fact with its own past times; it mailed no subscriber. - `managed_in` (ManagedIn, required, one of `oncall`, `status_page`) - `affected_component_ids` (array of string (uuid), required) - `updates` (array of IncidentUpdate, required): The timeline. - `id` (string (uuid)) - `incident_id` (string (uuid)) - `status` (IncidentStatus, one of `investigating`, `identified`, `monitoring`, `resolved`) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page or incident, or one on another page (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 409 — Publishing before the incident is resolved (`NOT_RESOLVED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/incidents/00000000-0000-0000-0000-000000000000/postmortem' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "## What happened\n\nA bad deploy raised API error rates for 25 minutes.", "published": true }' ``` --- Source: https://docs.evohub.io/status/list-maintenances.md # List a page's maintenance `GET https://evohub.io/api/v1/pages/{pageID}/maintenances` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `listMaintenances`. Every maintenance window of the page, with its affected components and timeline. Not paged. **Permission (API-key scope):** `status:maintenance:read`. ## Authorization Any one of: - `bearerAuth` (status:maintenance:read) - `apiKeyHeader` (status:maintenance:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 200 — The windows. Content type: `application/json` Type: `object` - `data` (array of Maintenance, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `title` (string, required) - `body` (string, required) - `status` (MaintenanceStatus, required, one of `scheduled`, `in_progress`, `completed`) - `scheduled_start` (string (date-time), required) - `scheduled_end` (string (date-time), required) - `completed_at` (string (date-time)): When the work actually finished; present once the window is completed. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `managed_in` (ManagedIn, required, one of `oncall`, `status_page`) - `affected_component_ids` (array of string (uuid), required) - `updates` (array of MaintenanceUpdate, required) - `id` (string (uuid)) - `maintenance_id` (string (uuid)) - `status` (MaintenanceStatus, one of `scheduled`, `in_progress`, `completed`) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/maintenances' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/create-maintenance.md # Schedule maintenance `POST https://evohub.io/api/v1/pages/{pageID}/maintenances` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `createMaintenance`. Schedules a maintenance window on the page. `title`, `scheduled_start` and `scheduled_end` are required. With a `body`, confirmed subscribers who follow an affected component (or the whole page) are mailed. **Permission (API-key scope):** `status:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (status:maintenance:write) - `apiKeyHeader` (status:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `title` (string, required) - `body` (string) - `status` (MaintenanceStatus, one of `scheduled`, `in_progress`, `completed`) - `scheduled_start` (string (date-time), required) - `scheduled_end` (string (date-time), required) - `affected_component_ids` (array of string (uuid)): Create only; ignored on update. Components of this page; any other id is skipped. - `managed_in` (ManagedIn, one of `oncall`, `status_page`): Create only; ignored on update. ## Responses ### 201 — The window. Content type: `application/json` Type: `object` - `data` (Maintenance, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `title` (string, required) - `body` (string, required) - `status` (MaintenanceStatus, required, one of `scheduled`, `in_progress`, `completed`) - `scheduled_start` (string (date-time), required) - `scheduled_end` (string (date-time), required) - `completed_at` (string (date-time)): When the work actually finished; present once the window is completed. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `managed_in` (ManagedIn, required, one of `oncall`, `status_page`) - `affected_component_ids` (array of string (uuid), required) - `updates` (array of MaintenanceUpdate, required) - `id` (string (uuid)) - `maintenance_id` (string (uuid)) - `status` (MaintenanceStatus, one of `scheduled`, `in_progress`, `completed`) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page (`PAGE_NOT_FOUND`, or `NOT_FOUND` for a page the caller cannot see). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/maintenances' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "Expect up to five minutes of read-only mode.", "title": "Database upgrade", "managed_in": "status_page", "scheduled_end": "2026-10-13T00:00:00Z", "scheduled_start": "2026-10-12T22:00:00Z", "affected_component_ids": [ "a7c1e3f5-2b4d-4c6e-8f0a-1b3d5f7a9c2e" ] }' ``` --- Source: https://docs.evohub.io/status/delete-maintenance.md # Delete maintenance `DELETE https://evohub.io/api/v1/pages/{pageID}/maintenances/{id}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `deleteMaintenance`. Deletes the window and its timeline. **Permission (API-key scope):** `status:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (status:maintenance:write) - `apiKeyHeader` (status:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page or maintenance window, or one on another page (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/maintenances/00000000-0000-0000-0000-000000000000' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/update-maintenance.md # Edit maintenance `PATCH https://evohub.io/api/v1/pages/{pageID}/maintenances/{id}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `updateMaintenance`. Edits the window; a field left out (or empty) keeps its value, so `body` cannot be cleared here. Moving it to `completed` also closes the timeline with "This maintenance has been completed.", once. Nobody is mailed. **Permission (API-key scope):** `status:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (status:maintenance:write) - `apiKeyHeader` (status:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). ## Request body (required) Content type: `application/json` Type: `MaintenanceWrite` - `title` (string) - `body` (string) - `status` (MaintenanceStatus, one of `scheduled`, `in_progress`, `completed`) - `scheduled_start` (string (date-time)) - `scheduled_end` (string (date-time)) - `completed_at` (string (date-time)): With `status` completed, when the work finished (between `scheduled_start` and now). Left out, now. - `affected_component_ids` (array of string (uuid)): Create only; ignored on update. Components of this page; any other id is skipped. - `managed_in` (ManagedIn, one of `oncall`, `status_page`): Create only; ignored on update. ## Responses ### 200 — The window. Content type: `application/json` Type: `object` - `data` (Maintenance, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `title` (string, required) - `body` (string, required) - `status` (MaintenanceStatus, required, one of `scheduled`, `in_progress`, `completed`) - `scheduled_start` (string (date-time), required) - `scheduled_end` (string (date-time), required) - `completed_at` (string (date-time)): When the work actually finished; present once the window is completed. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `managed_in` (ManagedIn, required, one of `oncall`, `status_page`) - `affected_component_ids` (array of string (uuid), required) - `updates` (array of MaintenanceUpdate, required) - `id` (string (uuid)) - `maintenance_id` (string (uuid)) - `status` (MaintenanceStatus, one of `scheduled`, `in_progress`, `completed`) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page or maintenance window, or one on another page (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/maintenances/00000000-0000-0000-0000-000000000000' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "status": "completed" }' ``` --- Source: https://docs.evohub.io/status/add-maintenance-update.md # Post a maintenance update `POST https://evohub.io/api/v1/pages/{pageID}/maintenances/{id}/updates` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `addMaintenanceUpdate`. Adds a timeline entry and moves the window to its `status`. Confirmed subscribers who follow an affected component (or the whole page) are mailed. **Permission (API-key scope):** `status:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (status:maintenance:write) - `apiKeyHeader` (status:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `status` (MaintenanceStatus, required, one of `scheduled`, `in_progress`, `completed`) - `body` (string, required, min length 1) ## Responses ### 200 — The window. Note: **200**, where the incident equivalent answers 201. Content type: `application/json` Type: `object` - `data` (Maintenance, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `title` (string, required) - `body` (string, required) - `status` (MaintenanceStatus, required, one of `scheduled`, `in_progress`, `completed`) - `scheduled_start` (string (date-time), required) - `scheduled_end` (string (date-time), required) - `completed_at` (string (date-time)): When the work actually finished; present once the window is completed. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `managed_in` (ManagedIn, required, one of `oncall`, `status_page`) - `affected_component_ids` (array of string (uuid), required) - `updates` (array of MaintenanceUpdate, required) - `id` (string (uuid)) - `maintenance_id` (string (uuid)) - `status` (MaintenanceStatus, one of `scheduled`, `in_progress`, `completed`) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page or maintenance window, or one on another page (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/maintenances/00000000-0000-0000-0000-000000000000/updates' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "The upgrade has started.", "status": "in_progress" }' ``` --- Source: https://docs.evohub.io/status/delete-maintenance-update.md # Delete a maintenance update `DELETE https://evohub.io/api/v1/pages/{pageID}/maintenances/{id}/updates/{updateID}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `deleteMaintenanceUpdate`. Removes one timeline entry. **Permission (API-key scope):** `status:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (status:maintenance:write) - `apiKeyHeader` (status:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). - `updateID` (string (uuid), required, example `d3f5b7c9-5e7a-4b9c-9d1e-4f6b8d0a2a51`): The timeline entry's id (a UUID). ## Responses ### 204 — Deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page or maintenance window, or one on another page (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/maintenances/00000000-0000-0000-0000-000000000000/updates/d3f5b7c9-5e7a-4b9c-9d1e-4f6b8d0a2a51' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/edit-maintenance-update.md # Edit a maintenance update's text `PATCH https://evohub.io/api/v1/pages/{pageID}/maintenances/{id}/updates/{updateID}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `editMaintenanceUpdate`. Changes one timeline entry's text. Nobody is mailed. **Permission (API-key scope):** `status:maintenance:write`. ## Authorization Any one of: - `bearerAuth` (status:maintenance:write) - `apiKeyHeader` (status:maintenance:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). - `updateID` (string (uuid), required, example `d3f5b7c9-5e7a-4b9c-9d1e-4f6b8d0a2a51`): The timeline entry's id (a UUID). ## Request body (required) Content type: `application/json` Type: `TimelineEntryEdit` - `body` (string, required, min length 1) ## Responses ### 200 — The window. Content type: `application/json` Type: `object` - `data` (Maintenance, required) - `id` (string (uuid), required) - `org_id` (string, required) - `page_id` (string (uuid), required) - `title` (string, required) - `body` (string, required) - `status` (MaintenanceStatus, required, one of `scheduled`, `in_progress`, `completed`) - `scheduled_start` (string (date-time), required) - `scheduled_end` (string (date-time), required) - `completed_at` (string (date-time)): When the work actually finished; present once the window is completed. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `managed_in` (ManagedIn, required, one of `oncall`, `status_page`) - `affected_component_ids` (array of string (uuid), required) - `updates` (array of MaintenanceUpdate, required) - `id` (string (uuid)) - `maintenance_id` (string (uuid)) - `status` (MaintenanceStatus, one of `scheduled`, `in_progress`, `completed`) - `body` (string) - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such page or maintenance window, or one on another page (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/maintenances/00000000-0000-0000-0000-000000000000/updates/d3f5b7c9-5e7a-4b9c-9d1e-4f6b8d0a2a51' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "The upgrade has started; read-only mode is on." }' ``` --- Source: https://docs.evohub.io/status/list-subscribers.md # List subscribers `GET https://evohub.io/api/v1/pages/{pageID}/subscribers` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `listSubscribers`. Every subscriber of the page, newest first, pending and confirmed. Not paged. **Permission (API-key scope):** `status:subscriber:read`. ## Authorization Any one of: - `bearerAuth` (status:subscriber:read) - `apiKeyHeader` (status:subscriber:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 200 — The subscribers. Content type: `application/json` Type: `object` - `data` (array of Subscriber, required) - `id` (string (uuid)) - `email` (string (email)) - `status` (string, one of `pending`, `confirmed`): Only `confirmed` subscribers are mailed (double opt-in). - `component_ids` (array of string): Empty: the whole page. - `product_groups` (array of string): Groups (products) followed. - `created_at` (string (date-time)) - `confirmed_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/subscribers' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/delete-subscriber.md # Remove a subscriber `DELETE https://evohub.io/api/v1/pages/{pageID}/subscribers/{id}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `deleteSubscriber`. Removes a subscriber of this page; they get no more mail. A subscriber of another page is not found (404), as is one that does not exist. **Permission (API-key scope):** `status:subscriber:write`. ## Authorization Any one of: - `bearerAuth` (status:subscriber:write) - `apiKeyHeader` (status:subscriber:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). - `id` (string (uuid), required): The incident's, maintenance window's or subscriber's id (a UUID). ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/subscribers/00000000-0000-0000-0000-000000000000' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/update-subscription-settings.md # Update subscription settings `PATCH https://evohub.io/api/v1/pages/{pageID}/subscription-settings` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `updateSubscriptionSettings`. Turns the public Subscribe form on or off and sets the reply-to address of subscriber mail. A field left out keeps its stored value; `reply_to_email` `null` or `""` clears the address. **Permission (API-key scope):** `status:subscriber:write`. ## Authorization Any one of: - `bearerAuth` (status:subscriber:write) - `apiKeyHeader` (status:subscriber:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `subscriptions_enabled` (boolean) - `reply_to_email` (string (email) | null) ## Responses ### 200 — The page. Content type: `application/json` Type: `object` - `data` (Page, required) - `id` (string (uuid), required) - `org_id` (string, required) - `team_id` (string, required): The team that manages the page; `""` for the whole organization. Never affects the public page. - `slug` (string, required): Unique in the organization. - `name` (string, required) - `logo_url` (string) - `logo_dark_url` (string): The logo the public page shows in dark mode; absent when the logo is used in both themes. - `favicon_url` (string) - `brand_color` (string): `#rrggbb`. - `custom_domain` (string) - `is_public` (boolean, required): Whether visitors can read it. - `theme` (string, required, one of `auto`, `light`, `dark`) - `site_theme` (SiteTheme, required) - `font` (string, one of ``, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Source Sans 3`, `IBM Plex Sans`, `Nunito Sans`, `Work Sans`, `Manrope`, `DM Sans`, `Plus Jakarta Sans`, `Figtree`, `Geist`, `Poppins`, `Merriweather`, `Source Serif 4`, `Lora`): `""` is the system font. - `radius` (string, one of `none`, `small`, `medium`, `large`, default `medium`) - `background` (string, one of `default`, `tinted`, `slate`, `warm`, default `default`) - `mode` (string, one of `system`, `light`, `dark`, default `system`): The same setting as the page's `theme` (`system` there is `auto`). - `subscriptions_enabled` (boolean) - `reply_to_email` (string): Where replies to subscriber emails go. - `page_description` (string) - `footer_text` (string) - `support_url` (string) - `support_label` (string) - `privacy_url` (string) - `terms_url` (string) - `ga_tag` (string): The older field for the Google Analytics id, kept in step with `analytics.ga4_measurement_id`. - `search_indexable` (boolean) - `history_window_days` (integer, one of `30`, `90`): How far back the public page shows past incidents. - `product_groups` (boolean): Groups fold into one line each on the public page. - `open_groups` (array of string): Groups that start open. - `product_pages` (boolean): Each group has a page of its own. - `header_links` (array of HeaderLink, max items 5) - `label` (string, required, min length 1, max length 40): One line of text. - `url` (string (uri), required, max length 2048): An `https://` URL. - `show_page_name` (boolean): `false` shows the logo alone. - `analytics` (AnalyticsSettings) - `enabled` (boolean): Whether visitor analytics are collected. - `retention_days` (integer, one of `30`, `90`, `180`, `365`, `730`): How long daily counts are kept. - `ga4_measurement_id` (string): A Google Analytics 4 id (`G-…`) the public page loads, or `""`. - `honor_privacy_signals` (boolean): Load Google Analytics only for visitors who sent neither `DNT: 1` nor `Sec-GPC: 1`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit of the page's settings. Send it back on an update to be refused (409 `EDIT_CONFLICT`) if the page changed since you read it. - `success` (boolean, required, value `true`) ### 400 — The body is not JSON, or has a field this endpoint does not take (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/subscription-settings' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "reply_to_email": "support@acme.com", "subscriptions_enabled": true }' ``` --- Source: https://docs.evohub.io/status/get-domain.md # Get the custom domain `GET https://evohub.io/api/v1/pages/{pageID}/domain` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getDomain`. The page's custom domain, its live state and the DNS records it needs. `status` is `none` when no domain is set; `active` once the domain serves the page over HTTPS. **Permission (API-key scope):** `status:page:read`. ## Authorization Any one of: - `bearerAuth` (status:page:read) - `apiKeyHeader` (status:page:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 200 — The domain. Content type: `application/json` Type: `object` - `data` (Domain, required) - `domain` (string | null, required) - `status` (string, required, one of `none`, `pending`, `verifying`, `active`, `error`) - `records` (array of DnsRecord): The DNS records to create at your DNS provider. - `type` (string, value `CNAME`) - `name` (string) - `value` (string) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/domain' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/set-domain.md # Set the custom domain `POST https://evohub.io/api/v1/pages/{pageID}/domain` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `setDomain`. Serves the page on a subdomain you own (an apex domain such as `example.com` is refused). Create the returned CNAME records at your DNS provider; the domain goes `pending` → `verifying` → `active` on its own. Setting a new domain replaces the old one. A custom domain on a public page is billed per hour while it is `active`. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `domain` (string, required, max length 253): A subdomain with at least three labels. ## Responses ### 201 — The domain. Content type: `application/json` Type: `object` - `data` (Domain, required) - `domain` (string | null, required) - `status` (string, required, one of `none`, `pending`, `verifying`, `active`, `error`) - `records` (array of DnsRecord): The DNS records to create at your DNS provider. - `type` (string, value `CNAME`) - `name` (string) - `value` (string) - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 502 — Cloudflare refused or failed the registration (`CLOUDFLARE_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 503 — Custom domains are not configured (`PROVISIONING_DISABLED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 422 — The domain cannot be connected, for example one the certificate provider refuses (`DOMAIN_REFUSED`); the message says why. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/domain' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "domain": "status.acme.com" }' ``` --- Source: https://docs.evohub.io/status/delete-domain.md # Remove the custom domain `DELETE https://evohub.io/api/v1/pages/{pageID}/domain` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `deleteDomain`. Stops serving the page on its custom domain. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 204 — Removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/domain' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/get-page-analytics.md # Get a page's visitor analytics `GET https://evohub.io/api/v1/pages/{pageID}/analytics` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getPageAnalytics`. Views, visitors, feed reads and AI requests per day, and the top routes, products, referrers, countries, devices, feeds and AI agents. Counts are cookieless and first-party. The range is whole UTC days, `from` and `to` inclusive, by default the last 30 days, at most 366. **Permission (API-key scope):** `status:page:read`. ## Authorization Any one of: - `bearerAuth` (status:page:read) - `apiKeyHeader` (status:page:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Query parameters - `from` (string (date)): First UTC day (`YYYY-MM-DD`). - `to` (string (date)): Last UTC day (`YYYY-MM-DD`), inclusive. Default today. ## Responses ### 200 — The analytics. Content type: `application/json` Type: `object` - `data` (PageAnalytics, required) - `from` (string (date)) - `to` (string (date)) - `enabled` (boolean): Whether the page collects analytics now. - `totals` (object) - `views` (integer) - `visitors` (integer) - `feed_reads` (integer) - `ai_requests` (integer) - `subscribers_confirmed` (integer) - `series` (array of object) - `day` (string (date)) - `views` (integer) - `visitors` (integer) - `routes` (array of object) - `route` (string) - `views` (integer) - `products` (array of object) - `product` (string) - `views` (integer) - `referrers` (array of object) - `host` (string) - `views` (integer) - `countries` (array of object) - `code` (string) - `views` (integer) - `devices` (array of object) - `device` (string) - `views` (integer) - `feeds` (array of object) - `kind` (string) - `count` (integer) - `ai` (array of object) - `kind` (string) - `count` (integer) - `ai_agents` (array of object) - `agent` (string) - `count` (integer) - `success` (boolean, required, value `true`) ### 400 — `from`/`to` is not a date, `from` is after `to`, or the range is longer than 366 days (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/analytics' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/get-page-hourly-views.md # Get a page's views by the hour `GET https://evohub.io/api/v1/pages/{pageID}/analytics/hourly` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getPageHourlyViews`. The page's views per UTC hour, every hour in the range present (0 when none). `from` and `to` go together: dates (`to` inclusive) or RFC 3339 instants (`to` exclusive), widened to whole hours. Default the last 7 days, at most 14. **Permission (API-key scope):** `status:page:read`. ## Authorization Any one of: - `bearerAuth` (status:page:read) - `apiKeyHeader` (status:page:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Query parameters - `from` (string, example `2026-10-08`): `YYYY-MM-DD` or RFC 3339. - `to` (string, example `2026-10-09`): `YYYY-MM-DD` (inclusive) or RFC 3339 (exclusive). ## Responses ### 200 — Hourly views. Content type: `application/json` Type: `object` - `data` (object, required) - `range` (AnalyticsRange) - `from` (string (date-time)) - `to` (string (date-time)) - `granularity` (string, one of `hour`, `day`) - `tz` (string, value `UTC`) - `page_id` (string) - `series` (array of object) - `bucket` (string (date-time)) - `views` (integer) - `success` (boolean, required, value `true`) ### 400 — `from`/`to` or `compare` is not valid (`VALIDATION_FAILED`, each field in `details`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string) - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/analytics/hourly' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/export-page-analytics.md # Export analytics as CSV `GET https://evohub.io/api/v1/pages/{pageID}/analytics/export.csv` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `exportPageAnalytics`. One report as CSV (RFC 4180, CRLF), up to 5000 rows. A cell that starts with `=`, `+`, `-` or `@` is prefixed with `'` so spreadsheets do not run it. Range as for the analytics read. **Permission (API-key scope):** `status:page:read`. ## Authorization Any one of: - `bearerAuth` (status:page:read) - `apiKeyHeader` (status:page:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Query parameters - `report` (string, one of `overview`, `routes`, `products`, `referrers`, `countries`, `devices`, `feeds`, `ai`, `ai_agents`, default `overview`): `overview` is one row per day; the others are one row per value. - `from` (string (date)): First UTC day. - `to` (string (date)): Last UTC day, inclusive. ## Responses ### 200 — The CSV, as an attachment named `---.csv`. Content type: `text/csv` Type: `string` ### 400 — Unknown `report`, or a bad range (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/analytics/export.csv' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/update-analytics-settings.md # Update analytics settings `PATCH https://evohub.io/api/v1/pages/{pageID}/analytics/settings` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `updateAnalyticsSettings`. Turns visitor analytics on or off, sets how long daily counts are kept, the Google Analytics 4 id the public page loads (`""` clears it) and whether privacy signals are honoured. A field left out is unchanged. **Permission (API-key scope):** `status:page:write`. ## Authorization Any one of: - `bearerAuth` (status:page:write) - `apiKeyHeader` (status:page:write) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Request body (required) Content type: `application/json` Type: `object` - `enabled` (boolean) - `retention_days` (integer, one of `30`, `90`, `180`, `365`, `730`) - `ga4_measurement_id` (string, pattern `^(G-[A-Z0-9]{4,20})?$`) - `honor_privacy_signals` (boolean) ## Responses ### 200 — The page. Content type: `application/json` Type: `object` - `data` (Page, required) - `id` (string (uuid), required) - `org_id` (string, required) - `team_id` (string, required): The team that manages the page; `""` for the whole organization. Never affects the public page. - `slug` (string, required): Unique in the organization. - `name` (string, required) - `logo_url` (string) - `logo_dark_url` (string): The logo the public page shows in dark mode; absent when the logo is used in both themes. - `favicon_url` (string) - `brand_color` (string): `#rrggbb`. - `custom_domain` (string) - `is_public` (boolean, required): Whether visitors can read it. - `theme` (string, required, one of `auto`, `light`, `dark`) - `site_theme` (SiteTheme, required) - `font` (string, one of ``, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Source Sans 3`, `IBM Plex Sans`, `Nunito Sans`, `Work Sans`, `Manrope`, `DM Sans`, `Plus Jakarta Sans`, `Figtree`, `Geist`, `Poppins`, `Merriweather`, `Source Serif 4`, `Lora`): `""` is the system font. - `radius` (string, one of `none`, `small`, `medium`, `large`, default `medium`) - `background` (string, one of `default`, `tinted`, `slate`, `warm`, default `default`) - `mode` (string, one of `system`, `light`, `dark`, default `system`): The same setting as the page's `theme` (`system` there is `auto`). - `subscriptions_enabled` (boolean) - `reply_to_email` (string): Where replies to subscriber emails go. - `page_description` (string) - `footer_text` (string) - `support_url` (string) - `support_label` (string) - `privacy_url` (string) - `terms_url` (string) - `ga_tag` (string): The older field for the Google Analytics id, kept in step with `analytics.ga4_measurement_id`. - `search_indexable` (boolean) - `history_window_days` (integer, one of `30`, `90`): How far back the public page shows past incidents. - `product_groups` (boolean): Groups fold into one line each on the public page. - `open_groups` (array of string): Groups that start open. - `product_pages` (boolean): Each group has a page of its own. - `header_links` (array of HeaderLink, max items 5) - `label` (string, required, min length 1, max length 40): One line of text. - `url` (string (uri), required, max length 2048): An `https://` URL. - `show_page_name` (boolean): `false` shows the logo alone. - `analytics` (AnalyticsSettings) - `enabled` (boolean): Whether visitor analytics are collected. - `retention_days` (integer, one of `30`, `90`, `180`, `365`, `730`): How long daily counts are kept. - `ga4_measurement_id` (string): A Google Analytics 4 id (`G-…`) the public page loads, or `""`. - `honor_privacy_signals` (boolean): Load Google Analytics only for visitors who sent neither `DNT: 1` nor `Sec-GPC: 1`. - `created_at` (string (date-time), required) - `updated_at` (string (date-time), required) - `version` (integer): Goes up by one on every edit of the page's settings. Send it back on an update to be refused (409 `EDIT_CONFLICT`) if the page changed since you read it. - `success` (boolean, required, value `true`) ### 400 — A field is missing or invalid (`VALIDATION_ERROR`); the message names it, or the body is not valid (`INVALID_BODY`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — No such item, or one the caller cannot see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/analytics/settings' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "enabled": true, "retention_days": 365, "ga4_measurement_id": "G-ABC123XYZ9" }' ``` --- Source: https://docs.evohub.io/status/get-status-analytics.md # Get analytics across pages `GET https://evohub.io/api/v1/pages/analytics` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getStatusAnalytics`. Every page the caller can see, together: views, visitors, feed and AI reads, subscriber growth (signed up, confirmed, unsubscribed, removed) and incidents published with time to first publication, as totals, a series, per page and top lists. `from` and `to` go together: dates (`to` inclusive) or RFC 3339 instants, widened to whole UTC days; default the last 30 days, at most 366. Two days or less gives an hourly series. `compare` adds the previous period's or last year's headline numbers. **Permission (API-key scope):** `status:page:read`. ## Authorization Any one of: - `bearerAuth` (status:page:read) - `apiKeyHeader` (status:page:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `from` (string, example `2026-09-10`): `YYYY-MM-DD` or RFC 3339. - `to` (string, example `2026-10-09`): `YYYY-MM-DD` (inclusive) or RFC 3339. - `team_id` (string): Only this team's pages. A team the caller cannot see gives an empty list. - `page_id` (string (uuid)): Only this page (404 if the caller cannot see it). - `compare` (string, one of `previous`, `year`) ## Responses ### 200 — The analytics. Content type: `application/json` Type: `object` - `data` (OrgAnalytics, required) - `range` (AnalyticsRange) - `from` (string (date-time)) - `to` (string (date-time)) - `granularity` (string, one of `hour`, `day`) - `tz` (string, value `UTC`) - `totals` (object) - `pages` (integer) - `views` (integer) - `visitors` (integer) - `feed_reads` (integer) - `ai_requests` (integer) - `subscribers_confirmed` (integer) - `subscribers` (SubscriberEvents) - `signed_up` (integer) - `confirmed` (integer) - `unsubscribed` (integer) - `removed` (integer) - `net` (integer) - `incidents` (IncidentStats) - `published` (integer) - `by_impact` (array of object) - `impact` (IncidentImpact, one of `none`, `minor`, `major`, `critical`) - `count` (integer) - `time_to_first_publication` (DurationStats) - `series` (array of object): Hourly for a range of two days or less (then the per-day counters are `null`), daily otherwise. - `bucket` (string (date-time)) - `views` (integer) - `visitors` (integer | null) - `feed_reads` (integer | null) - `ai_requests` (integer | null) - `subscribers` (SubscriberEvents | null) - One of: - SubscriberEvents - `signed_up` (integer) - `confirmed` (integer) - `unsubscribed` (integer) - `removed` (integer) - `net` (integer) - null - `incidents_published` (integer) - `pages` (array of object) - `page_id` (string) - `name` (string) - `slug` (string) - `team_id` (string) - `custom_domain` (string | null) - `analytics_enabled` (boolean) - `views` (integer) - `visitors` (integer) - `feed_reads` (integer) - `ai_requests` (integer) - `subscribers_confirmed` (integer) - `subscribers` (SubscriberEvents) - `signed_up` (integer) - `confirmed` (integer) - `unsubscribed` (integer) - `removed` (integer) - `net` (integer) - `incidents` (IncidentStats) - `published` (integer) - `by_impact` (array of object) - `impact` (IncidentImpact, one of `none`, `minor`, `major`, `critical`) - `count` (integer) - `time_to_first_publication` (DurationStats) - `routes` (array of object) - `route` (string) - `views` (integer) - `referrers` (array of object) - `host` (string) - `views` (integer) - `countries` (array of object) - `code` (string) - `views` (integer) - `devices` (array of object) - `device` (string) - `views` (integer) - `feeds` (array of object) - `kind` (string) - `count` (integer) - `ai` (array of object) - `kind` (string) - `count` (integer) - `ai_agents` (array of object) - `agent` (string) - `count` (integer) - `compare` (object | null) - One of: - object - `mode` (string, one of `previous`, `year`) - `from` (string (date-time)) - `to` (string (date-time)) - `views` (integer) - `visitors` (integer) - `feed_reads` (integer) - `ai_requests` (integer) - `subscribers` (SubscriberEvents) - `signed_up` (integer) - `confirmed` (integer) - `unsubscribed` (integer) - `removed` (integer) - `net` (integer) - `incidents` (IncidentStats) - `published` (integer) - `by_impact` (array of object) - `impact` (IncidentImpact, one of `none`, `minor`, `major`, `critical`) - `count` (integer) - `time_to_first_publication` (DurationStats) - null - `success` (boolean, required, value `true`) ### 400 — `from`/`to` or `compare` is not valid (`VALIDATION_FAILED`, each field in `details`). Content type: `application/json` Type: `ValidationError` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `request_id` (string) - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 404 — `page_id` is not a page the caller can see (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/analytics' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/get-pages-summary.md # Summarize every page `GET https://evohub.io/api/v1/pages/summary` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getPagesSummary`. Every page the caller can see (the page list's scope, `team_id` too) with the status visitors see, its component count, the active incident and the maintenance in progress, plus the organization's counts of active incidents, maintenance today and confirmed subscribers. Incident, maintenance and subscriber figures need their own read permission too: without `status:incident:read`, `status:maintenance:read` or `status:subscriber:read` they are `null`, not an error. **Permission (API-key scope):** `status:page:read`. ## Authorization Any one of: - `bearerAuth` (status:page:read) - `apiKeyHeader` (status:page:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `team_id` (string): Only this team's pages. A team the caller cannot see gives an empty list. ## Responses ### 200 — The summary. Content type: `application/json` Type: `object` - `data` (PagesSummary, required) - `pages` (array of object) - `id` (string (uuid)) - `name` (string) - `status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `components` (integer) - `active_incident` (object | null) - One of: - object - `id` (string) - `title` (string) - `status` (IncidentStatus, one of `investigating`, `identified`, `monitoring`, `resolved`) - null - `active_maintenance` (object | null) - One of: - object - `id` (string) - `title` (string) - `starts_at` (string (date-time)) - `ends_at` (string (date-time)) - null - `active_incidents` (integer | null): `null` without `status:incident:read`. - `maintenance_today` (integer | null): `null` without `status:maintenance:read`. - `subscribers` (integer | null): Confirmed subscribers; `null` without `status:subscriber:read`. - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/summary' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/get-pages-overview.md # Get every page's state `GET https://evohub.io/api/v1/pages/overview` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getPagesOverview`. One entry per page the caller can see (the page list's scope, `team_id` too): its domain state, component and subscriber counts, the overall status as the public page works it out, the newest open incident and the current or next maintenance. Live monitor states are read with a short time budget; a slow Uptime falls back to the stored status. **Permission (API-key scope):** `status:page:read`. ## Authorization Any one of: - `bearerAuth` (status:page:read) - `apiKeyHeader` (status:page:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `team_id` (string): Only this team's pages. A team the caller cannot see gives an empty list. ## Responses ### 200 — The pages' state. Content type: `application/json` Type: `object` - `data` (array of PageOverview, required) - `page_id` (string (uuid)) - `domain_status` (string): The custom domain's state (`none`, `pending`, `verifying`, `active`, `error`). - `components` (integer) - `affected_components` (integer): Components visitors see as anything but operational. - `overall_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `subscribers` (integer) - `live_incidents` (integer) - `live_incident` (object | null): The newest open incident. - One of: - object - `id` (string) - `title` (string) - `impact` (IncidentImpact, one of `none`, `minor`, `major`, `critical`) - `updated_at` (string (date-time)) - null - `maintenance` (object | null): The window in progress, else the next one scheduled. - One of: - object - `id` (string) - `title` (string) - `status` (MaintenanceStatus, one of `scheduled`, `in_progress`, `completed`) - `scheduled_start` (string (date-time)) - `scheduled_end` (string (date-time)) - null - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/pages/overview' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/list-status-audit-logs.md # List the Status audit log `GET https://evohub.io/api/v1/status-audit-logs` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `listStatusAuditLogs`. Who changed what in Status, newest first, for the whole organization (not narrowed by team). Paged with `limit` and `offset`. **Permission (API-key scope):** `status:audit:read`. ## Authorization Any one of: - `bearerAuth` (status:audit:read) - `apiKeyHeader` (status:audit:read) Where: - `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `limit` (integer, default `50`, min 1, max 200): Page size. A value over 200 gives 200; one that is not a positive number gives 50. - `offset` (integer, default `0`, min 0) ## Responses ### 200 — The entries. Content type: `application/json` Type: `object` - `data` (array of AuditLog, required) - `id` (string (uuid)) - `org_id` (string) - `actor_user_id` (string): The user or API key that did it; `system` for EvoHub itself. - `action` (string): `resource.verb`, for example `page.created`, `component.status_changed`, `incident.resolved`, `subscriber.deleted`, `domain.added`. - `resource_type` (string, one of `page`, `component`, `incident`, `maintenance`, `subscriber`, `org`) - `resource_id` (string) - `metadata` (object) - Other keys: any - `created_at` (string (date-time)) - `success` (boolean, required, value `true`) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/status-audit-logs' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/status/get-public-page.md # Read a public page `GET https://evohub.io/api/v1/public/pages/{pageID}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getPublicPage`. What a public page shows: the page, its overall status, components with their live status and uptime, open incidents, upcoming and running maintenance, and incidents resolved within the page's history window. No API key; a private page answers 404. ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 200 — The page as visitors see it. Content type: `application/json` Type: `object` - `data` (PublicPage, required) - `page` (object): The page as visitors see it: the admin page object without `org_id`, `team_id`, `reply_to_email` and the analytics retention, with `analytics` reduced to `{enabled, ga4_measurement_id, honor_privacy_signals}`. - `overall_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `components` (array of object) - `id` (string) - `name` (string) - `description` (string) - `source` (string) - `status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `position` (integer) - `group` (string) - `uptime_percentage` (number): Monitor-linked components: uptime over the page's history window. - `uptime_history` (array of object) - `date` (string (date)) - `status` (string) - `up_ratio` (number) - `active_incidents` (array of object): Open incidents: `id, title, impact, status, started_at, resolved_at, affected_component_ids, updates[], postmortem, postmortem_published_at`. - `scheduled_maintenances` (array of object) - `past_incidents` (array of object): Resolved within the page's history window. - `product_pages` (array of object) - `name` (string) - `slug` (string) - `component_ids` (array of string) - `logo_url` (string) - `success` (boolean, required, value `true`) ### 404 — No public page with that id or domain (`NOT_FOUND`). A private page answers the same. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/public/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42' ``` --- Source: https://docs.evohub.io/status/get-public-page-by-domain.md # Read a public page by domain `GET https://evohub.io/api/v1/public/pages/by-domain/{domain}` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getPublicPageByDomain`. The same as reading it by id, found by its custom domain. No API key. ## Path parameters - `domain` (string, required, example `status.example.com`): The page's custom domain, for example `status.example.com` (case-insensitive). ## Responses ### 200 — The page as visitors see it. Content type: `application/json` Type: `object` - `data` (PublicPage, required) - `page` (object): The page as visitors see it: the admin page object without `org_id`, `team_id`, `reply_to_email` and the analytics retention, with `analytics` reduced to `{enabled, ga4_measurement_id, honor_privacy_signals}`. - `overall_status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `components` (array of object) - `id` (string) - `name` (string) - `description` (string) - `source` (string) - `status` (ComponentStatus, one of `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`) - `position` (integer) - `group` (string) - `uptime_percentage` (number): Monitor-linked components: uptime over the page's history window. - `uptime_history` (array of object) - `date` (string (date)) - `status` (string) - `up_ratio` (number) - `active_incidents` (array of object): Open incidents: `id, title, impact, status, started_at, resolved_at, affected_component_ids, updates[], postmortem, postmortem_published_at`. - `scheduled_maintenances` (array of object) - `past_incidents` (array of object): Resolved within the page's history window. - `product_pages` (array of object) - `name` (string) - `slug` (string) - `component_ids` (array of string) - `logo_url` (string) - `success` (boolean, required, value `true`) ### 404 — No public page with that id or domain (`NOT_FOUND`). A private page answers the same. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/public/pages/by-domain/status.example.com' ``` --- Source: https://docs.evohub.io/status/get-public-rss-feed.md # RSS feed `GET https://evohub.io/api/v1/public/pages/{pageID}/feed.rss` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getPublicRssFeed`. The page's incident and maintenance updates as RSS, newest first, within the page's history window. Cached for 60 seconds. No API key. ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 200 — The RSS feed. Content type: `application/rss+xml` Type: `string` ### 404 — No public page with that id or domain (`NOT_FOUND`). A private page answers the same. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/public/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/feed.rss' ``` --- Source: https://docs.evohub.io/status/get-public-rss-feed-by-domain.md # RSS feed by domain `GET https://evohub.io/api/v1/public/pages/by-domain/{domain}/feed.rss` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getPublicRssFeedByDomain`. The same RSS feed, found by the page's custom domain. No API key. ## Path parameters - `domain` (string, required, example `status.example.com`): The page's custom domain, for example `status.example.com` (case-insensitive). ## Responses ### 200 — The RSS feed. Content type: `application/rss+xml` Type: `string` ### 404 — No public page with that id or domain (`NOT_FOUND`). A private page answers the same. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/public/pages/by-domain/status.example.com/feed.rss' ``` --- Source: https://docs.evohub.io/status/get-public-atom-feed.md # Atom feed `GET https://evohub.io/api/v1/public/pages/{pageID}/feed.atom` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getPublicAtomFeed`. The page's incident and maintenance updates as Atom, newest first, within the page's history window. Cached for 60 seconds. No API key. ## Path parameters - `pageID` (string (uuid), required, example `3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42`): The status page's id (a UUID). ## Responses ### 200 — The Atom feed. Content type: `application/atom+xml` Type: `string` ### 404 — No public page with that id or domain (`NOT_FOUND`). A private page answers the same. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/public/pages/3f6c2a1e-8b4d-4f7a-9c21-5d0e7a9b1c42/feed.atom' ``` --- Source: https://docs.evohub.io/status/get-public-atom-feed-by-domain.md # Atom feed by domain `GET https://evohub.io/api/v1/public/pages/by-domain/{domain}/feed.atom` Part of the [Status API](https://docs.evohub.io/status.md) reference · operationId `getPublicAtomFeedByDomain`. The same Atom feed, found by the page's custom domain. No API key. ## Path parameters - `domain` (string, required, example `status.example.com`): The page's custom domain, for example `status.example.com` (case-insensitive). ## Responses ### 200 — The Atom feed. Content type: `application/atom+xml` Type: `string` ### 404 — No public page with that id or domain (`NOT_FOUND`). A private page answers the same. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required): Machine-readable code. Branch on this. - `message` (string, required): Human-readable explanation. - `request_id` (string): This request's id, also in `X-Request-ID`. ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/public/pages/by-domain/status.example.com/feed.atom' ``` --- Source: https://docs.evohub.io/support.md # Support API Version 1.0 · OpenAPI 3.1.0 Open support tickets with EvoHub from scripts and tools, follow the conversation, reply, and read the support audit log of your organization. **Authentication.** Every endpoint takes an EvoHub API key, sent as `Authorization: Bearer evohub_…` or as `X-API-Key: evohub_…`. A missing or invalid key gets **401**; a valid key without the scope an endpoint needs gets **403**, never 401. See the [API overview](/api-overview), [API keys and scopes](/api-keys-and-scopes) and [Errors](/errors). **Whose tickets.** Tickets are private to whoever opened them. The ticket endpoints need no scope, because they only ever reach the caller's own tickets: - a **personal key** acts as the person who created it, so it sees and replies to that person's tickets, including the ones they opened in the console; - an **organization key** acts as itself, so it sees only the tickets opened with that key. A ticket that belongs to someone else in the organization answers **404** `NOT_FOUND`, exactly like one that does not exist. The organization-wide audit log is the one endpoint that reaches beyond the caller's own tickets, and it needs the `support:audit:read` scope. **Responses.** Success is `{"success": true, "data": …}`. Errors are `{"success": false, "error": {"code", "message", "request_id"}}`; `request_id` is the request's id, also sent in the `X-Request-ID` header — quote it to support. A refusal from the API gateway itself (401, 429) carries the same `error` object without `success`. Timestamps are RFC 3339 in UTC. Ids carry a type prefix (`tkt_`, `msg_`, `aud_`). ## Servers - `https://evohub.io` ## Authentication - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Tickets Open tickets, read the conversation and reply. - [GET /api/v1/support/tickets](https://docs.evohub.io/support/list-tickets.md): List your tickets - [POST /api/v1/support/tickets](https://docs.evohub.io/support/create-ticket.md): Open a ticket - [GET /api/v1/support/tickets/{id}](https://docs.evohub.io/support/get-ticket.md): Read a ticket and its conversation - [POST /api/v1/support/tickets/{id}/messages](https://docs.evohub.io/support/reply-to-ticket.md): Reply to a ticket ## Audit log What happened to tickets, and who did it. - [GET /api/v1/support/tickets/{id}/audit-logs](https://docs.evohub.io/support/list-ticket-audit-logs.md): List a ticket's audit log - [GET /api/v1/support/audit-logs](https://docs.evohub.io/support/list-support-audit-logs.md): List the organization's support audit log --- Source: https://docs.evohub.io/support/list-tickets.md # List your tickets `GET https://evohub.io/api/v1/support/tickets` Part of the [Support API](https://docs.evohub.io/support.md) reference · operationId `listTickets`. The caller's own tickets, newest first (see **Whose tickets** above). The conversation is not included; read one ticket to get its messages. `data` is `null` when the caller has no tickets. Requires no scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The caller's tickets. Content type: `application/json` Type: `object` - `success` (boolean, value `true`) - `data` (array of Ticket | null) - `id` (string, example `tkt_3f9a1c2e7b5d4a60`) - `org_id` (string) - `user_id` (string): Who opened the ticket — the person behind a personal key, or the organization key's own id. - `subject` (string) - `type` (string, one of `support`, `bug`, `feature`, `question`) - `status` (string, one of `open`, `pending`, `resolved`, `closed`): `open` when new or after the caller replies. EvoHub support moves it to `pending`, `resolved` or `closed`. - `priority` (string, one of `low`, `normal`, `high`, `urgent`) - `reporter_email` (string): As sent when the ticket was opened; `""` when it was not. - `reporter_name` (string) - `org_name` (string) - `url` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/support/tickets' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/support/create-ticket.md # Open a ticket `POST https://evohub.io/api/v1/support/tickets` Part of the [Support API](https://docs.evohub.io/support.md) reference · operationId `createTicket`. Opens a ticket. `body` becomes the first message of the conversation. A new ticket's `status` is `open`. - `subject` and `body` are required (surrounding spaces are trimmed); `subject` is at most 200 characters. - `type` is `support` (the default), `bug`, `feature` or `question`. - `priority` is `low`, `normal` (the default), `high` or `urgent`. - `reporter_email`, `reporter_name`, `org_name` and `url` are stored as sent, to tell the support team who to answer and where the problem was seen. They are optional. Requires no scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `CreateTicketRequest` - `subject` (string, required, max length 200) - `body` (string, required): The first message of the conversation. - `type` (string, one of `support`, `bug`, `feature`, `question`, default `support`) - `priority` (string, one of `low`, `normal`, `high`, `urgent`, default `normal`) - `reporter_email` (string) - `reporter_name` (string) - `org_name` (string) - `url` (string): The page or resource the ticket is about. ## Responses ### 201 — The ticket was opened. Content type: `application/json` Type: `object` - `success` (boolean, value `true`) - `data` (Ticket) - `id` (string, example `tkt_3f9a1c2e7b5d4a60`) - `org_id` (string) - `user_id` (string): Who opened the ticket — the person behind a personal key, or the organization key's own id. - `subject` (string) - `type` (string, one of `support`, `bug`, `feature`, `question`) - `status` (string, one of `open`, `pending`, `resolved`, `closed`): `open` when new or after the caller replies. EvoHub support moves it to `pending`, `resolved` or `closed`. - `priority` (string, one of `low`, `normal`, `high`, `urgent`) - `reporter_email` (string): As sent when the ticket was opened; `""` when it was not. - `reporter_name` (string) - `org_name` (string) - `url` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) ### 400 — The body is not JSON (`INVALID_JSON`), or a field breaks a rule (`VALIDATION_ERROR`; the message names it): `subject` or `body` is missing or blank, `type` or `priority` is not one of the allowed values, or `subject` is longer than 200 characters (this one also carries `details` naming the field). Content type: `application/json` Type: `Error | ValidationFailed` - One of: - Error - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) - ValidationFailed - `success` (boolean, value `false`) - `error` (object, required) - `code` (string, required, value `VALIDATION_ERROR`) - `message` (string, required) - `details` (array of object, required) - `field` (string) - `message` (string) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/support/tickets' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "url": "https://evohub.io/oncall/integrations", "body": "Alerts from our Prometheus integration arrive 5–10 minutes late\nsince about 07:30 UTC. Request id of one late delivery:\nreq_9a8b7c6d5e4f3a2b.\n", "type": "bug", "subject": "Webhook deliveries delayed since this morning", "org_name": "Acme", "priority": "high", "reporter_name": "Dana Ops", "reporter_email": "ops@acme.example" }' ``` --- Source: https://docs.evohub.io/support/get-ticket.md # Read a ticket and its conversation `GET https://evohub.io/api/v1/support/tickets/{id}` Part of the [Support API](https://docs.evohub.io/support.md) reference · operationId `getTicket`. One of the caller's tickets with every message on it, oldest first. A message from the caller has `author_type` `user`; an answer from EvoHub support has `author_type` `admin`. Requires no scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `tkt_3f9a1c2e7b5d4a60`): The ticket's ID. ## Responses ### 200 — The ticket and its messages. Content type: `application/json` Type: `object` - `success` (boolean, value `true`) - `data` (TicketWithMessages) - `ticket` (Ticket) - `id` (string, example `tkt_3f9a1c2e7b5d4a60`) - `org_id` (string) - `user_id` (string): Who opened the ticket — the person behind a personal key, or the organization key's own id. - `subject` (string) - `type` (string, one of `support`, `bug`, `feature`, `question`) - `status` (string, one of `open`, `pending`, `resolved`, `closed`): `open` when new or after the caller replies. EvoHub support moves it to `pending`, `resolved` or `closed`. - `priority` (string, one of `low`, `normal`, `high`, `urgent`) - `reporter_email` (string): As sent when the ticket was opened; `""` when it was not. - `reporter_name` (string) - `org_name` (string) - `url` (string) - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `messages` (array of TicketMessage): Oldest first. - `id` (string, example `msg_8e2d4f6a1b3c5d70`) - `ticket_id` (string) - `author_id` (string) - `author_type` (string, one of `user`, `admin`): `user` for the ticket's owner, `admin` for EvoHub support. - `body` (string) - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 404 — The ticket does not exist, or it is not one of the caller's own (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/support/tickets/tkt_3f9a1c2e7b5d4a60' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/support/reply-to-ticket.md # Reply to a ticket `POST https://evohub.io/api/v1/support/tickets/{id}/messages` Part of the [Support API](https://docs.evohub.io/support.md) reference · operationId `replyToTicket`. Adds a message from the caller to one of their tickets. Replying sets the ticket's `status` back to `open`, whatever it was, so a resolved or closed ticket is reopened by answering it. `body` is required (surrounding spaces are trimmed). Requires no scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `tkt_3f9a1c2e7b5d4a60`): The ticket's ID. ## Request body (required) Content type: `application/json` Type: `object` - `body` (string, required): The message. ## Responses ### 201 — The message was added. Content type: `application/json` Type: `object` - `success` (boolean, value `true`) - `data` (TicketMessage) - `id` (string, example `msg_8e2d4f6a1b3c5d70`) - `ticket_id` (string) - `author_id` (string) - `author_type` (string, one of `user`, `admin`): `user` for the ticket's owner, `admin` for EvoHub support. - `body` (string) - `created_at` (string (date-time)) ### 400 — The body is not JSON (`INVALID_JSON`), or `body` is missing or blank (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 404 — The ticket does not exist, or it is not one of the caller's own (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/support/tickets/tkt_3f9a1c2e7b5d4a60/messages' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "body": "Confirmed, deliveries are on time again. Thank you!" }' ``` --- Source: https://docs.evohub.io/support/list-ticket-audit-logs.md # List a ticket's audit log `GET https://evohub.io/api/v1/support/tickets/{id}/audit-logs` Part of the [Support API](https://docs.evohub.io/support.md) reference · operationId `listTicketAuditLogs`. What happened to one of the caller's tickets, newest first, at most 100 entries: `ticket.created`, `message.added`, and the changes EvoHub support made (`status.changed`, `priority.changed`). An entry written by support has `actor_type` `admin` and `user_id` `admin`. `data` is `[]` when the ticket has no entries yet. Requires no scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `tkt_3f9a1c2e7b5d4a60`): The ticket's ID. ## Responses ### 200 — The ticket's audit entries. Content type: `application/json` Type: `AuditLogList` - `success` (boolean, value `true`) - `data` (array of AuditLog) - `id` (string, example `aud_1a2b3c4d5e6f7a8b`) - `org_id` (string) - `user_id` (string): Who acted; `admin` for EvoHub support. - `resource_type` (string, value `ticket`) - `resource_id` (string): The ticket's ID. - `action` (string, one of `ticket.created`, `message.added`, `status.changed`, `priority.changed`) - `actor_type` (string, one of `user`, `admin`) - `details` (object): What changed: `subject`, `type` and `priority` for `ticket.created`; `message_id` for `message.added`; `status` or `priority` for the changes. Left out when empty. - Other keys: any - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 404 — The ticket does not exist, or it is not one of the caller's own (`NOT_FOUND`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/support/tickets/tkt_3f9a1c2e7b5d4a60/audit-logs' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/support/list-support-audit-logs.md # List the organization's support audit log `GET https://evohub.io/api/v1/support/audit-logs` Part of the [Support API](https://docs.evohub.io/support.md) reference · operationId `listSupportAuditLogs`. Support activity across the whole organization — every member's tickets, not just the caller's — newest first, at most 200 entries. The entries have the same shape and actions as a ticket's audit log. `data` is `[]` when there are no entries. Requires the `support:audit:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The organization's support audit entries. Content type: `application/json` Type: `AuditLogList` - `success` (boolean, value `true`) - `data` (array of AuditLog) - `id` (string, example `aud_1a2b3c4d5e6f7a8b`) - `org_id` (string) - `user_id` (string): Who acted; `admin` for EvoHub support. - `resource_type` (string, value `ticket`) - `resource_id` (string): The ticket's ID. - `action` (string, one of `ticket.created`, `message.added`, `status.changed`, `priority.changed`) - `actor_type` (string, one of `user`, `admin`) - `details` (object): What changed: `subject`, `type` and `priority` for `ticket.created`; `message_id` for `message.added`; `status` or `priority` for the changes. Left out when empty. - Other keys: any - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `success` (boolean, value `false`): Sent by Support; the gateway's own refusals leave it out. - `error` (object, required) - `code` (string, required, example `NOT_FOUND`) - `message` (string, required, example `ticket not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/support/audit-logs' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime.md # Uptime API Version 1.0 · OpenAPI 3.1.0 Manage EvoHub Uptime from scripts and CI: create and change monitors, pause them, read their check history, uptime and SLA figures, mute alerts for planned work, and route alerts to notification channels. **Authentication.** Every endpoint except the heartbeat ping takes an EvoHub API key, sent as `Authorization: Bearer evohub_…` or as `X-API-Key: evohub_…`. Each operation names the scope the key needs in its description and in `x-evohub-permission`. A missing or invalid key gets **401**; a valid key without the scope gets **403**, never 401. See the [API overview](/api-overview), [API keys and scopes](/api-keys-and-scopes) and [Errors](/errors). **Responses.** A successful answer carries its result in `data`. The analytics endpoint also sends `"success": true`. Errors carry an `error` object with a machine-readable `code`. Timestamps are RFC 3339 in UTC. **Teams.** A monitor either belongs to the whole organization (`team_id` is `""`) or to one team. A request sees the organization-wide monitors plus those of the teams it is in; an organization key scoped to a team sees that team's. A monitor the caller cannot see answers **404** `MONITOR_NOT_FOUND`, exactly like one that does not exist. **Heartbeat pings.** `/ping/{token}` is not an API-key endpoint: the token in the URL is the credential, so a cron job can call it with a bare `curl`. ## Servers - `https://evohub.io` ## Authentication - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Monitors Create, read, change, pause and delete monitors. - [GET /api/v1/monitors](https://docs.evohub.io/uptime/list-monitors.md): List monitors - [POST /api/v1/monitors](https://docs.evohub.io/uptime/create-monitor.md): Create a monitor - [GET /api/v1/monitors/attention](https://docs.evohub.io/uptime/list-monitors-needing-attention.md): List monitors that need attention - [GET /api/v1/monitors/{id}](https://docs.evohub.io/uptime/get-monitor.md): Get a monitor - [DELETE /api/v1/monitors/{id}](https://docs.evohub.io/uptime/delete-monitor.md): Delete a monitor - [PATCH /api/v1/monitors/{id}](https://docs.evohub.io/uptime/update-monitor.md): Update, pause or resume a monitor ## Checks and uptime A monitor's check results, outages and uptime figures. - [GET /api/v1/monitors/{id}/checks](https://docs.evohub.io/uptime/list-monitor-checks.md): List a monitor's checks - [GET /api/v1/monitors/{id}/incidents](https://docs.evohub.io/uptime/list-monitor-incidents.md): List a monitor's outages - [GET /api/v1/monitors/{id}/uptime](https://docs.evohub.io/uptime/get-monitor-uptime.md): Get a monitor's uptime - [GET /api/v1/monitors/{id}/uptime/daily](https://docs.evohub.io/uptime/get-monitor-daily-uptime.md): Get a monitor's uptime per day ## Analytics Uptime, latency and SLA across every monitor you can see. - [GET /api/v1/uptime/analytics](https://docs.evohub.io/uptime/get-uptime-analytics.md): Get uptime analytics ## Silence Mute alerts for the whole organization or for one monitor's maintenance window. - [GET /api/v1/silence](https://docs.evohub.io/uptime/get-organization-silence.md): Get the organization's silence - [PUT /api/v1/silence](https://docs.evohub.io/uptime/set-organization-silence.md): Silence all alerts - [DELETE /api/v1/silence](https://docs.evohub.io/uptime/clear-organization-silence.md): End the silence - [GET /api/v1/monitors/{id}/silence](https://docs.evohub.io/uptime/get-monitor-silence.md): Get a monitor's silence window - [PUT /api/v1/monitors/{id}/silence](https://docs.evohub.io/uptime/set-monitor-silence.md): Silence a monitor for a window - [DELETE /api/v1/monitors/{id}/silence](https://docs.evohub.io/uptime/clear-monitor-silence.md): Clear a monitor's silence window ## Notification channels Where alerts go besides On-Call, and which monitors use each channel. - [GET /api/v1/notification-channels](https://docs.evohub.io/uptime/list-notification-channels.md): List notification channels - [POST /api/v1/notification-channels](https://docs.evohub.io/uptime/create-notification-channel.md): Create a notification channel - [GET /api/v1/notification-channels/chat-apps](https://docs.evohub.io/uptime/list-chat-app-channels.md): List chat app channels - [DELETE /api/v1/notification-channels/{id}](https://docs.evohub.io/uptime/delete-notification-channel.md): Delete a notification channel - [GET /api/v1/monitors/{id}/channels](https://docs.evohub.io/uptime/list-monitor-channels.md): List a monitor's channels - [POST /api/v1/monitors/{id}/channels/{chId}](https://docs.evohub.io/uptime/link-channel-to-monitor.md): Link a channel to a monitor - [DELETE /api/v1/monitors/{id}/channels/{chId}](https://docs.evohub.io/uptime/unlink-channel-from-monitor.md): Unlink a channel from a monitor ## Weekly summary The weekly uptime summary email. - [GET /api/v1/uptime-digest](https://docs.evohub.io/uptime/get-weekly-summary-settings.md): Get the weekly summary settings - [PUT /api/v1/uptime-digest](https://docs.evohub.io/uptime/set-weekly-summary-enabled.md): Turn the weekly summary on or off - [PUT /api/v1/uptime-digest/me](https://docs.evohub.io/uptime/set-my-weekly-summary-subscription.md): Subscribe or unsubscribe yourself - [POST /api/v1/uptime-digest/preview](https://docs.evohub.io/uptime/send-weekly-summary-preview.md): Email yourself a preview ## Audit log Who changed what in Uptime. - [GET /api/v1/uptime-audit-logs](https://docs.evohub.io/uptime/list-uptime-audit-logs.md): List Uptime audit log entries ## Heartbeat pings The URL a heartbeat monitor's job calls. - [GET /ping/{token}](https://docs.evohub.io/uptime/ping-heartbeat.md): Ping a heartbeat monitor - [POST /ping/{token}](https://docs.evohub.io/uptime/ping-heartbeat-post.md): Ping a heartbeat monitor (POST) - [HEAD /ping/{token}](https://docs.evohub.io/uptime/ping-heartbeat-head.md): Ping a heartbeat monitor (HEAD) --- Source: https://docs.evohub.io/uptime/list-monitors.md # List monitors `GET https://evohub.io/api/v1/monitors` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `listMonitors`. Every monitor the caller can see, newest first. Each monitor carries its 30-day uptime in `uptime_percent["30d"]` and `sla_breached_30d` (heartbeat monitors carry neither) and, while a silence window mutes it, `silenced_until`. `data` is `[]` when there are no monitors. Requires the `uptime:monitor:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `team_id` (string, example `none`): Narrow to one team's monitors, or `none` for only the organization-wide ones. It never widens what the caller can see. ## Responses ### 200 — The monitors. Content type: `application/json` Type: `object` - `data` (array of Monitor) - `id` (string): Starts with `mon_`. - `org_id` (string) - `team_id` (string): The owning team, or `""` for the whole organization. - `name` (string) - `url` (string): The target; `""` for a heartbeat monitor. Shown with credentials masked: userinfo becomes `****:****@` and the values of `token`, `key`, `secret`, `password`, `sig`, `signature`, `auth`, `api_key` and `access_token` query parameters (any case, or a name ending in `_` or `-` plus one of them) become `****`. Checks use the URL as entered. - `type` (MonitorType, one of `http`, `keyword`, `tcp`, `icmp`, `heartbeat`) - `interval_seconds` (integer): How often it is checked; for a heartbeat, how often a ping is expected. - `timeout_seconds` (integer) - `locations` (array of string): Probe locations. Empty means EvoHub chooses. - `keyword` (string): Text a `keyword` monitor looks for in the body. - `expected_status` (integer): The status code an HTTP check expects when there are no `status_code` assertions. - `is_active` (boolean): `false` while paused: no checks and no alerts. - `failure_threshold` (integer): Failed checks in a row before the monitor is down. - `consecutive_failures` (integer) - `last_checked_at` (string (date-time)) - `last_status` (MonitorStatus, one of `up`, `down`, `degraded`, `unknown`) - `silenced_until` (string (date-time)): Present on the list while a silence window mutes the monitor; when it ends. - `http_method` (string) - `http_headers` (array of object): The request headers by name, sorted. A header's value is never returned, since it often carries a token. `has_value` says whether one is stored. - `name` (string) - `has_value` (boolean) - `http_body` (string) - `http_auth_user` (string) - `has_http_auth_password` (boolean): Whether a Basic Auth password is stored. The password itself is never returned. - `check_ssl` (boolean): Warn before the TLS certificate expires. - `ssl_expiry_threshold_days` (integer): Days before expiry to warn. - `ssl_expiry_days` (integer): Days until the certificate expires, from the last check. - `tags` (array of string) - `group_id` (string) - `response_time_threshold_ms` (integer) - `oncall_policy_id` (string): The On-Call escalation policy its alerts go to. - `re_alert_minutes` (integer): Minutes between repeat alerts while down. - `grace_seconds` (integer): Heartbeat only. How late a ping may be. - `ping_token` (string): Heartbeat only. The job calls `https://evohub.io/ping/{ping_token}`. - `last_ping_at` (string (date-time)): Heartbeat only. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every change. Send it back with an update to detect a concurrent edit. - `assertions` (array of Assertion) - `type` (string, required, one of `status_code`, `body`, `json`, `header`) - `field` (string, max length 1024) - `operator` (string, required, one of `in`, `contains`, `not_contains`, `equals`, `not_equals`, `exists`) - `value` (string, max length 1024) - `degraded_threshold_ms` (integer | null): A passing check slower than this is degraded. - `alert_on_degraded` (boolean): Raise a lower-severity alert while degraded. - `consecutive_degraded` (integer) - `sla_target` (number | null): The uptime percentage the monitor is held to, e.g. 99.9. - `last_failure_reason` (string) - `last_failure_at` (string (date-time)) - `uptime_percent` (object): Uptime percentage per window. A single monitor carries `24h`, `7d`, `30d` and `90d`; the list carries only `30d`. `null` for a window with no checks. Absent for heartbeat monitors. - Other keys: number | null - `sla_breached_30d` (boolean): The 30-day uptime is below `sla_target`. Absent without a target or 30-day data, and for heartbeat monitors. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/monitors' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/create-monitor.md # Create a monitor `POST https://evohub.io/api/v1/monitors` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `createMonitor`. Creates a monitor. `name`, `type`, `interval_seconds` and `timeout_seconds` are required. `url` depends on the type: | Type | `url` | | --- | --- | | `http`, `keyword` | An `http://` or `https://` URL. | | `tcp` | `host:port`, `[v6]:port`, `tcp://host:port`, or an http(s) URL (port 80 or 443). | | `icmp` | A host name or IP address. A port or URL is accepted and the host is used. | | `heartbeat` | Not used. | A new monitor is paused unless you send `"is_active": true`. Its `last_status` is `unknown` until the first check. A heartbeat monitor gets a `ping_token` at creation, which never changes. Its job calls `https://evohub.io/ping/{ping_token}`. Defaults when a field is left out or `0`: `expected_status` 200, `failure_threshold` 1, `http_method` `GET`, `ssl_expiry_threshold_days` 30, `re_alert_minutes` 15, and for a heartbeat `grace_seconds` 60. `team_id` must be a team the caller is in, or `""` for the whole organization; otherwise **403** `NOT_IN_TEAM`. Requires the `uptime:monitor:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `CreateMonitorRequest` - `name` (string, required, min length 1, max length 255): Not blank. - `team_id` (string): A team the caller is in, or `""` (the default) for the whole organization. - `type` (MonitorType, required, one of `http`, `keyword`, `tcp`, `icmp`, `heartbeat`) - `url` (string): The target; see the table above. For `http` and `keyword`, an `http://` or `https://` address with a host — any other scheme is refused. Not used by `heartbeat`. - `interval_seconds` (integer, required, min 15, max 2592000) - `timeout_seconds` (integer, required, min 1, max 60) - `grace_seconds` (integer, min 0, max 86400): Heartbeat only. Defaults to 60. - `locations` (array of string, max items 5) - `keyword` (string) - `expected_status` (integer, default `200`, min 100, max 599) - `is_active` (boolean, default `false`): Send `true` to start checking at once. - `failure_threshold` (integer, default `1`, min 1, max 10) - `http_method` (string, one of `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`, default `GET`) - `http_headers` (object): Request headers sent with every check, by name. Values are stored but never returned. - Other keys: string - `http_body` (string) - `http_auth_user` (string) - `http_auth_password` (string, write-only): Basic Auth password sent with every check. Stored but never returned; responses carry `has_http_auth_password`. - `check_ssl` (boolean) - `ssl_expiry_threshold_days` (integer, default `30`, min 1, max 365) - `tags` (array of string, max items 20) - `group_id` (string) - `response_time_threshold_ms` (integer, min 1) - `oncall_policy_id` (string): The On-Call escalation policy its alerts go to. - `re_alert_minutes` (integer, default `15`, min 1, max 1440) - `assertions` (array of Assertion, max items 10): `http` monitors only. - `type` (string, required, one of `status_code`, `body`, `json`, `header`) - `field` (string, max length 1024) - `operator` (string, required, one of `in`, `contains`, `not_contains`, `equals`, `not_equals`, `exists`) - `value` (string, max length 1024) - `degraded_threshold_ms` (integer, min 0, max 60000): `0` or absent means never degraded. - `alert_on_degraded` (boolean) - `sla_target` (number, min 0, max 100): `0` or absent means no target. ## Responses ### 201 — The monitor was created. Content type: `application/json` Type: `MonitorEnvelope` - `data` (Monitor) - `id` (string): Starts with `mon_`. - `org_id` (string) - `team_id` (string): The owning team, or `""` for the whole organization. - `name` (string) - `url` (string): The target; `""` for a heartbeat monitor. Shown with credentials masked: userinfo becomes `****:****@` and the values of `token`, `key`, `secret`, `password`, `sig`, `signature`, `auth`, `api_key` and `access_token` query parameters (any case, or a name ending in `_` or `-` plus one of them) become `****`. Checks use the URL as entered. - `type` (MonitorType, one of `http`, `keyword`, `tcp`, `icmp`, `heartbeat`) - `interval_seconds` (integer): How often it is checked; for a heartbeat, how often a ping is expected. - `timeout_seconds` (integer) - `locations` (array of string): Probe locations. Empty means EvoHub chooses. - `keyword` (string): Text a `keyword` monitor looks for in the body. - `expected_status` (integer): The status code an HTTP check expects when there are no `status_code` assertions. - `is_active` (boolean): `false` while paused: no checks and no alerts. - `failure_threshold` (integer): Failed checks in a row before the monitor is down. - `consecutive_failures` (integer) - `last_checked_at` (string (date-time)) - `last_status` (MonitorStatus, one of `up`, `down`, `degraded`, `unknown`) - `silenced_until` (string (date-time)): Present on the list while a silence window mutes the monitor; when it ends. - `http_method` (string) - `http_headers` (array of object): The request headers by name, sorted. A header's value is never returned, since it often carries a token. `has_value` says whether one is stored. - `name` (string) - `has_value` (boolean) - `http_body` (string) - `http_auth_user` (string) - `has_http_auth_password` (boolean): Whether a Basic Auth password is stored. The password itself is never returned. - `check_ssl` (boolean): Warn before the TLS certificate expires. - `ssl_expiry_threshold_days` (integer): Days before expiry to warn. - `ssl_expiry_days` (integer): Days until the certificate expires, from the last check. - `tags` (array of string) - `group_id` (string) - `response_time_threshold_ms` (integer) - `oncall_policy_id` (string): The On-Call escalation policy its alerts go to. - `re_alert_minutes` (integer): Minutes between repeat alerts while down. - `grace_seconds` (integer): Heartbeat only. How late a ping may be. - `ping_token` (string): Heartbeat only. The job calls `https://evohub.io/ping/{ping_token}`. - `last_ping_at` (string (date-time)): Heartbeat only. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every change. Send it back with an update to detect a concurrent edit. - `assertions` (array of Assertion) - `type` (string, required, one of `status_code`, `body`, `json`, `header`) - `field` (string, max length 1024) - `operator` (string, required, one of `in`, `contains`, `not_contains`, `equals`, `not_equals`, `exists`) - `value` (string, max length 1024) - `degraded_threshold_ms` (integer | null): A passing check slower than this is degraded. - `alert_on_degraded` (boolean): Raise a lower-severity alert while degraded. - `consecutive_degraded` (integer) - `sla_target` (number | null): The uptime percentage the monitor is held to, e.g. 99.9. - `last_failure_reason` (string) - `last_failure_at` (string (date-time)) - `uptime_percent` (object): Uptime percentage per window. A single monitor carries `24h`, `7d`, `30d` and `90d`; the list carries only `30d`. `null` for a window with no checks. Absent for heartbeat monitors. - Other keys: number | null - `sla_breached_30d` (boolean): The 30-day uptime is below `sla_target`. Absent without a target or 30-day data, and for heartbeat monitors. ### 400 — The body is not JSON (`INVALID_JSON`), or a field breaks a rule (`VALIDATION_ERROR`; the message names it): a missing required field, a blank `name`, a value out of range, a `url` the type cannot probe (an `http`/`keyword` url must be `http://` or `https://`), assertions on a non-http monitor or an invalid assertion, `degraded_threshold_ms` outside 1–60000, or `sla_target` outside (0, 100]. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks `uptime:monitor:write` (`FORBIDDEN`), or `team_id` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/monitors' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "url": "https://api.acme.example/health", "name": "Checkout API", "tags": [ "checkout", "production" ], "type": "http", "is_active": true, "assertions": [ { "type": "status_code", "value": "200-299", "operator": "in" }, { "type": "json", "field": "$.status", "value": "ok", "operator": "equals" } ], "sla_target": 99.9, "timeout_seconds": 10, "interval_seconds": 60, "failure_threshold": 2, "degraded_threshold_ms": 1500 }' ``` --- Source: https://docs.evohub.io/uptime/list-monitors-needing-attention.md # List monitors that need attention `GET https://evohub.io/api/v1/monitors/attention` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `listMonitorsNeedingAttention`. The caller's active monitors that are down or degraded: down ones first, then by how long they have been in that state, oldest first. `since` is when the monitor went down (from its open outage) or when its slow alert was raised. `incident_id` and `cause` are filled for a down monitor with an open outage, and only when the caller also holds `uptime:incident:read`; otherwise they are `null`. Requires the `uptime:monitor:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `team_id` (string, example `none`): Narrow to one team's monitors, or `none` for only the organization-wide ones. It never widens what the caller can see. - `limit` (integer, default `20`, min 1, max 100): How many monitors to return. Values above 100 are read as 100. ## Responses ### 200 — The monitors needing attention; an empty list when all is well. Content type: `application/json` Type: `object` - `data` (array of AttentionMonitor) - `id` (string, required) - `name` (string, required) - `last_status` (string, required, one of `down`, `degraded`) - `since` (string (date-time) | null, required) - `incident_id` (string | null, required) - `cause` (string | null, required) ### 400 — `limit` is not a positive integer (`INVALID_LIMIT`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/monitors/attention' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/get-monitor.md # Get a monitor `GET https://evohub.io/api/v1/monitors/{id}` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `getMonitor`. One monitor, with its uptime over the last 24 hours, 7, 30 and 90 days in `uptime_percent` (a window with no checks is `null`) and, when it has an `sla_target`, whether the 30-day uptime is below it in `sla_breached_30d`. Heartbeat monitors carry neither. Requires the `uptime:monitor:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. ## Responses ### 200 — The monitor. Content type: `application/json` Type: `MonitorEnvelope` - `data` (Monitor) - `id` (string): Starts with `mon_`. - `org_id` (string) - `team_id` (string): The owning team, or `""` for the whole organization. - `name` (string) - `url` (string): The target; `""` for a heartbeat monitor. Shown with credentials masked: userinfo becomes `****:****@` and the values of `token`, `key`, `secret`, `password`, `sig`, `signature`, `auth`, `api_key` and `access_token` query parameters (any case, or a name ending in `_` or `-` plus one of them) become `****`. Checks use the URL as entered. - `type` (MonitorType, one of `http`, `keyword`, `tcp`, `icmp`, `heartbeat`) - `interval_seconds` (integer): How often it is checked; for a heartbeat, how often a ping is expected. - `timeout_seconds` (integer) - `locations` (array of string): Probe locations. Empty means EvoHub chooses. - `keyword` (string): Text a `keyword` monitor looks for in the body. - `expected_status` (integer): The status code an HTTP check expects when there are no `status_code` assertions. - `is_active` (boolean): `false` while paused: no checks and no alerts. - `failure_threshold` (integer): Failed checks in a row before the monitor is down. - `consecutive_failures` (integer) - `last_checked_at` (string (date-time)) - `last_status` (MonitorStatus, one of `up`, `down`, `degraded`, `unknown`) - `silenced_until` (string (date-time)): Present on the list while a silence window mutes the monitor; when it ends. - `http_method` (string) - `http_headers` (array of object): The request headers by name, sorted. A header's value is never returned, since it often carries a token. `has_value` says whether one is stored. - `name` (string) - `has_value` (boolean) - `http_body` (string) - `http_auth_user` (string) - `has_http_auth_password` (boolean): Whether a Basic Auth password is stored. The password itself is never returned. - `check_ssl` (boolean): Warn before the TLS certificate expires. - `ssl_expiry_threshold_days` (integer): Days before expiry to warn. - `ssl_expiry_days` (integer): Days until the certificate expires, from the last check. - `tags` (array of string) - `group_id` (string) - `response_time_threshold_ms` (integer) - `oncall_policy_id` (string): The On-Call escalation policy its alerts go to. - `re_alert_minutes` (integer): Minutes between repeat alerts while down. - `grace_seconds` (integer): Heartbeat only. How late a ping may be. - `ping_token` (string): Heartbeat only. The job calls `https://evohub.io/ping/{ping_token}`. - `last_ping_at` (string (date-time)): Heartbeat only. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every change. Send it back with an update to detect a concurrent edit. - `assertions` (array of Assertion) - `type` (string, required, one of `status_code`, `body`, `json`, `header`) - `field` (string, max length 1024) - `operator` (string, required, one of `in`, `contains`, `not_contains`, `equals`, `not_equals`, `exists`) - `value` (string, max length 1024) - `degraded_threshold_ms` (integer | null): A passing check slower than this is degraded. - `alert_on_degraded` (boolean): Raise a lower-severity alert while degraded. - `consecutive_degraded` (integer) - `sla_target` (number | null): The uptime percentage the monitor is held to, e.g. 99.9. - `last_failure_reason` (string) - `last_failure_at` (string (date-time)) - `uptime_percent` (object): Uptime percentage per window. A single monitor carries `24h`, `7d`, `30d` and `90d`; the list carries only `30d`. `null` for a window with no checks. Absent for heartbeat monitors. - Other keys: number | null - `sla_breached_30d` (boolean): The 30-day uptime is below `sla_target`. Absent without a target or 30-day data, and for heartbeat monitors. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist, or belongs to a team the caller cannot see (`MONITOR_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/delete-monitor.md # Delete a monitor `DELETE https://evohub.io/api/v1/monitors/{id}` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `deleteMonitor`. Deletes the monitor with its checks, outages, silence window and channel links. It cannot be undone; to stop checks but keep the history, pause the monitor instead. Requires the `uptime:monitor:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. ## Responses ### 204 — The monitor was deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist, or belongs to a team the caller cannot see (`MONITOR_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/update-monitor.md # Update, pause or resume a monitor `PATCH https://evohub.io/api/v1/monitors/{id}` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `updateMonitor`. Changes only the fields you send. To **pause** a monitor send `{"is_active": false}`; to **resume** it, `{"is_active": true}`. - `team_id`: `""` moves the monitor to the whole organization; a team must be one the caller is in. - `assertions` replaces the whole list; `[]` clears it. - `degraded_threshold_ms: 0` and `sla_target: 0` clear those settings. - `url` is checked against the type only when `url` or `type` is sent. The masked URL a read returns can be sent back: each `****` keeps the stored userinfo or parameter value, so editing another part of the URL keeps its credentials. - A monitor changed to `heartbeat` gets a `ping_token`; an existing heartbeat keeps its token. - Send the `version` you read to guard against a concurrent edit: if the monitor has changed since, nothing is saved and the answer is **409** `EDIT_CONFLICT`. Without `version` the edit always applies. Requires the `uptime:monitor:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. ## Request body (required) Content type: `application/json` Type: `UpdateMonitorRequest` Every field is optional; only the fields sent change. Rules are those of `CreateMonitorRequest`. - `name` (string, min length 1, max length 255) - `team_id` (string): `""` moves the monitor to the whole organization. - `type` (MonitorType, one of `http`, `keyword`, `tcp`, `icmp`, `heartbeat`) - `url` (string) - `interval_seconds` (integer, min 15, max 2592000) - `timeout_seconds` (integer, min 1, max 60) - `grace_seconds` (integer, min 0, max 86400) - `locations` (array of string, max items 5) - `keyword` (string) - `expected_status` (integer, min 100, max 599) - `is_active` (boolean): `false` pauses the monitor, `true` resumes it. - `failure_threshold` (integer, min 1, max 10) - `http_method` (string, one of `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`) - `http_headers` (object | array of object): Replaces the header set. A header left out is removed. A header sent with an empty value keeps its stored value (names match case-insensitively), and any other value replaces it. Because of that, a monitor read from the API can be sent back unchanged. Accepts the object form, or the response's list form with an optional `value` per entry. - One of: - object - Other keys: string - array of object - `name` (string, required) - `value` (string) - `http_body` (string) - `http_auth_user` (string) - `http_auth_password` (string, write-only): Omit it to keep the stored password; `""` removes it. - `check_ssl` (boolean) - `ssl_expiry_threshold_days` (integer, min 1, max 365) - `tags` (array of string, max items 20): Replaces all tags. - `group_id` (string) - `response_time_threshold_ms` (integer, min 1) - `oncall_policy_id` (string) - `re_alert_minutes` (integer, min 1, max 1440) - `assertions` (array of Assertion, max items 10): Replaces the whole list; `[]` clears it. - `type` (string, required, one of `status_code`, `body`, `json`, `header`) - `field` (string, max length 1024) - `operator` (string, required, one of `in`, `contains`, `not_contains`, `equals`, `not_equals`, `exists`) - `value` (string, max length 1024) - `degraded_threshold_ms` (integer, min 0, max 60000): `0` clears it. - `alert_on_degraded` (boolean) - `sla_target` (number, min 0, max 100): `0` clears it. - `version` (integer): The monitor's `version` as you read it. When it no longer matches, nothing is saved and the answer is 409 `EDIT_CONFLICT`. ## Responses ### 200 — The monitor as saved. Content type: `application/json` Type: `MonitorEnvelope` - `data` (Monitor) - `id` (string): Starts with `mon_`. - `org_id` (string) - `team_id` (string): The owning team, or `""` for the whole organization. - `name` (string) - `url` (string): The target; `""` for a heartbeat monitor. Shown with credentials masked: userinfo becomes `****:****@` and the values of `token`, `key`, `secret`, `password`, `sig`, `signature`, `auth`, `api_key` and `access_token` query parameters (any case, or a name ending in `_` or `-` plus one of them) become `****`. Checks use the URL as entered. - `type` (MonitorType, one of `http`, `keyword`, `tcp`, `icmp`, `heartbeat`) - `interval_seconds` (integer): How often it is checked; for a heartbeat, how often a ping is expected. - `timeout_seconds` (integer) - `locations` (array of string): Probe locations. Empty means EvoHub chooses. - `keyword` (string): Text a `keyword` monitor looks for in the body. - `expected_status` (integer): The status code an HTTP check expects when there are no `status_code` assertions. - `is_active` (boolean): `false` while paused: no checks and no alerts. - `failure_threshold` (integer): Failed checks in a row before the monitor is down. - `consecutive_failures` (integer) - `last_checked_at` (string (date-time)) - `last_status` (MonitorStatus, one of `up`, `down`, `degraded`, `unknown`) - `silenced_until` (string (date-time)): Present on the list while a silence window mutes the monitor; when it ends. - `http_method` (string) - `http_headers` (array of object): The request headers by name, sorted. A header's value is never returned, since it often carries a token. `has_value` says whether one is stored. - `name` (string) - `has_value` (boolean) - `http_body` (string) - `http_auth_user` (string) - `has_http_auth_password` (boolean): Whether a Basic Auth password is stored. The password itself is never returned. - `check_ssl` (boolean): Warn before the TLS certificate expires. - `ssl_expiry_threshold_days` (integer): Days before expiry to warn. - `ssl_expiry_days` (integer): Days until the certificate expires, from the last check. - `tags` (array of string) - `group_id` (string) - `response_time_threshold_ms` (integer) - `oncall_policy_id` (string): The On-Call escalation policy its alerts go to. - `re_alert_minutes` (integer): Minutes between repeat alerts while down. - `grace_seconds` (integer): Heartbeat only. How late a ping may be. - `ping_token` (string): Heartbeat only. The job calls `https://evohub.io/ping/{ping_token}`. - `last_ping_at` (string (date-time)): Heartbeat only. - `created_at` (string (date-time)) - `updated_at` (string (date-time)) - `version` (integer): Goes up by one on every change. Send it back with an update to detect a concurrent edit. - `assertions` (array of Assertion) - `type` (string, required, one of `status_code`, `body`, `json`, `header`) - `field` (string, max length 1024) - `operator` (string, required, one of `in`, `contains`, `not_contains`, `equals`, `not_equals`, `exists`) - `value` (string, max length 1024) - `degraded_threshold_ms` (integer | null): A passing check slower than this is degraded. - `alert_on_degraded` (boolean): Raise a lower-severity alert while degraded. - `consecutive_degraded` (integer) - `sla_target` (number | null): The uptime percentage the monitor is held to, e.g. 99.9. - `last_failure_reason` (string) - `last_failure_at` (string (date-time)) - `uptime_percent` (object): Uptime percentage per window. A single monitor carries `24h`, `7d`, `30d` and `90d`; the list carries only `30d`. `null` for a window with no checks. Absent for heartbeat monitors. - Other keys: number | null - `sla_breached_30d` (boolean): The 30-day uptime is below `sla_target`. Absent without a target or 30-day data, and for heartbeat monitors. ### 400 — The body is not JSON (`INVALID_JSON`) or a field breaks a rule (`VALIDATION_ERROR`), as for creating a monitor. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks `uptime:monitor:write` (`FORBIDDEN`), or `team_id` names a team the caller is not in (`NOT_IN_TEAM`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist, or belongs to a team the caller cannot see (`MONITOR_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 409 — `version` was sent and the monitor has changed since that version was read (`EDIT_CONFLICT`). Read it again and retry. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X PATCH 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "is_active": false }' ``` --- Source: https://docs.evohub.io/uptime/list-monitor-checks.md # List a monitor's checks `GET https://evohub.io/api/v1/monitors/{id}/checks` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `listMonitorChecks`. The monitor's most recent check results, newest first, one per probe location per check. A heartbeat monitor records a check only when its state changes. `data` is `[]` when the monitor has no checks yet. Requires the `uptime:monitor:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. ## Query parameters - `limit` (integer, default `100`, min 1, max 1000): How many checks to return. A value outside 1–1000, or not a number, is read as 100. ## Responses ### 200 — The checks. Content type: `application/json` Type: `object` - `data` (array of Check) - `id` (string) - `monitor_id` (string) - `org_id` (string) - `location` (string) - `status` (MonitorStatus, one of `up`, `down`, `degraded`, `unknown`) - `latency_ms` (integer) - `status_code` (integer) - `error` (string): Why the check failed. - `ssl_expiry_days` (integer) - `checked_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist, or belongs to a team the caller cannot see (`MONITOR_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19/checks' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/list-monitor-incidents.md # List a monitor's outages `GET https://evohub.io/api/v1/monitors/{id}/incidents` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `listMonitorIncidents`. Every outage the monitor has had, newest first. An outage opens when the monitor goes down and is resolved when it comes back up; one that is still open has no `resolved_at`. Outages are recorded by Uptime and cannot be created or changed through the API. `data` is `[]` when the monitor has never been down. Requires the `uptime:incident:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. ## Responses ### 200 — The outages. Content type: `application/json` Type: `object` - `data` (array of Incident) - `id` (string) - `monitor_id` (string) - `org_id` (string) - `started_at` (string (date-time)) - `resolved_at` (string (date-time)): Absent while the outage is open. - `duration_seconds` (integer): Present once resolved. - `cause` (string) - `last_alerted_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist, or belongs to a team the caller cannot see (`MONITOR_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19/incidents' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/get-monitor-uptime.md # Get a monitor's uptime `GET https://evohub.io/api/v1/monitors/{id}/uptime` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `getMonitorUptime`. The share of the monitor's checks over the last `days` days that were up, as a percentage rounded to two decimals. A degraded check counts as up. With no checks in the range the answer is `100`; use the per-day endpoint to tell "no data" apart. Requires the `uptime:monitor:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. ## Query parameters - `days` (integer, default `30`, min 1, max 365): How many days back to count. A value outside 1–365, or not a number, is read as 30. ## Responses ### 200 — The uptime. Content type: `application/json` Type: `object` - `data` (object) - `monitor_id` (string, required) - `days` (integer, required): The number of days counted. - `uptime_percent` (number, required): Percentage of up checks, 0–100. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist, or belongs to a team the caller cannot see (`MONITOR_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19/uptime' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/get-monitor-daily-uptime.md # Get a monitor's uptime per day `GET https://evohub.io/api/v1/monitors/{id}/uptime/daily` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `getMonitorDailyUptime`. One entry per UTC calendar day for the last `days` days, today included, oldest first — the data behind an uptime calendar. A day with no checks is `no_data`, never counted as up. Requires the `uptime:monitor:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. ## Query parameters - `days` (integer, default `90`, min 1, max 90): How many days to return. Values are clamped to 1–90; a value that is not a number is read as 90. ## Responses ### 200 — The days. Content type: `application/json` Type: `object` - `data` (object) - `monitor_id` (string, required) - `days` (array of DailyUptime, required) - `date` (string (date), required): The UTC calendar day. - `up_ratio` (number | null, required): Share of up checks, 0–1; `null` with no checks. - `status` (string, required, one of `operational`, `degraded`, `down`, `no_data`): `operational` when every check was up, `down` when none was, `degraded` in between, `no_data` without checks. - `checks` (integer, required) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist, or belongs to a team the caller cannot see (`MONITOR_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19/uptime/daily' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/get-uptime-analytics.md # Get uptime analytics `GET https://evohub.io/api/v1/uptime/analytics` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `getUptimeAnalytics`. Uptime, latency, downtime, outages and SLA results over a date range for every monitor the caller can see: organization totals, a time series, a row per monitor, the five monitors furthest below their SLA target, and a row per team. - With neither `from` nor `to`, the range is the last 30 UTC days, today included. - A date (`YYYY-MM-DD`) is a whole UTC day, and `to` is the last day included. An RFC 3339 time is an exact instant, and `to` is excluded. - The range may be at most 366 days. Ranges up to 48 hours that start within the last 89 days are answered in hourly buckets, all others in daily buckets of whole UTC days; `range` in the answer gives what was used. - A monitor without its own `sla_target` is held to `default_sla_target` (99.9). Requires the `uptime:monitor:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `from` (string, example `2026-09-01`): Start of the range. Send together with `to`. - `to` (string, example `2026-09-30`): End of the range. Send together with `from`. - `compare` (string, one of `previous`, `year`): Also return headline numbers for the period before (`previous`) or the same range a year earlier (`year`). - `team_id` (string, example `none`): Narrow to one team's monitors, or `none` for only the organization-wide ones. It never widens what the caller can see. ## Responses ### 200 — The analytics. Content type: `application/json` Type: `object` - `data` (UptimeAnalytics, required) - `range` (object): The window answered. Daily answers cover whole UTC days, so it can be wider than asked. - `from` (string (date-time)) - `to` (string (date-time)) - `granularity` (string, one of `hour`, `day`) - `tz` (string, value `UTC`) - `default_sla_target` (number) - `totals` (object) - `monitors` (integer) - `uptime_percent` (number | null): Check-weighted across all monitors. - `monitor_average_uptime_percent` (number | null): The plain mean of each monitor's own uptime. - `checks` (integer) - `up_checks` (integer) - `down_checks` (integer) - `degraded_checks` (integer) - `degraded_seconds` (integer) - `down_seconds` (integer) - `latency` (Latency) - `avg_ms` (number | null) - `p50_ms` (number | null) - `p95_ms` (number | null) - `sla` (object) - `met` (integer) - `missed` (integer) - `no_data` (integer) - `incidents` (IncidentStats) - `count` (integer) - `resolved` (integer) - `open_now` (integer) - `downtime_seconds` (number) - `mttr` (object): Time to recover, over resolved outages. - `count` (integer) - `p50_seconds` (number | null) - `p95_seconds` (number | null) - `avg_seconds` (number | null) - `series` (array of object) - `bucket` (string (date-time)) - `checks` (integer) - `up_checks` (integer) - `uptime_percent` (number | null) - `degraded_seconds` (integer) - `down_seconds` (integer) - `latency` (Latency) - `avg_ms` (number | null) - `p50_ms` (number | null) - `p95_ms` (number | null) - `incidents` (integer) - `by_monitor` (array of MonitorStats) - `monitor_id` (string) - `name` (string) - `type` (MonitorType, one of `http`, `keyword`, `tcp`, `icmp`, `heartbeat`) - `team_id` (string) - `is_active` (boolean) - `sla_target` (number | null) - `sla_target_source` (string, one of `monitor`, `default`): Whether the target is the monitor's own or `default_sla_target`. - `uptime_percent` (number | null) - `sla_gap` (number | null): Uptime minus target, in percentage points. - `sla_met` (boolean | null) - `checks` (integer) - `up_checks` (integer) - `degraded_seconds` (integer) - `down_seconds` (integer) - `latency` (Latency) - `incidents` (IncidentStats) - `worst_monitors` (array of MonitorStats): Up to five monitors furthest below their target, then lowest uptime. - `monitor_id` (string) - `name` (string) - `type` (MonitorType, one of `http`, `keyword`, `tcp`, `icmp`, `heartbeat`) - `team_id` (string) - `is_active` (boolean) - `sla_target` (number | null) - `sla_target_source` (string, one of `monitor`, `default`): Whether the target is the monitor's own or `default_sla_target`. - `uptime_percent` (number | null) - `sla_gap` (number | null): Uptime minus target, in percentage points. - `sla_met` (boolean | null) - `checks` (integer) - `up_checks` (integer) - `degraded_seconds` (integer) - `down_seconds` (integer) - `latency` (Latency) - `incidents` (IncidentStats) - `by_team` (array of object): One row per team; `team_id` `""` is the organization-wide monitors. - `team_id` (string) - `monitors` (integer) - `uptime_percent` (number | null) - `sla_missed` (integer) - `incidents` (integer) - `downtime_seconds` (number) - `degraded_seconds` (integer) - `coverage` (object): Days rolled up before latency and degraded or down time were recorded add nothing to those figures; this says how many there are. - `rows` (integer) - `rows_without_detail` (integer) - `compare` (null | object): The comparison period's headline numbers, or `null` without `compare`. - One of: - null - object - `mode` (string, one of `previous`, `year`) - `from` (string (date-time)) - `to` (string (date-time)) - `uptime_percent` (number | null) - `checks` (integer) - `degraded_seconds` (integer) - `latency` (Latency) - `avg_ms` (number | null) - `p50_ms` (number | null) - `p95_ms` (number | null) - `incidents` (IncidentStats) - `count` (integer) - `resolved` (integer) - `open_now` (integer) - `downtime_seconds` (number) - `mttr` (object): Time to recover, over resolved outages. - `count` (integer) - `p50_seconds` (number | null) - `p95_seconds` (number | null) - `avg_seconds` (number | null) - `success` (boolean, required, value `true`) ### 400 — `from`, `to` or `compare` is invalid (`VALIDATION_FAILED`, with `details` naming each field). Content type: `application/json` Type: `ValidationFailed` - `error` (object, required) - `code` (string, required, value `VALIDATION_FAILED`) - `message` (string, required) - `details` (array of object, required) - `field` (string) - `message` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/uptime/analytics' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/get-organization-silence.md # Get the organization's silence `GET https://evohub.io/api/v1/silence` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `getOrganizationSilence`. Whether all Uptime alerts of the organization are muted. `silenced_until` is `null`, or a time in the past, when they are not. Requires the `uptime:silence:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The organization's silence. Content type: `application/json` Type: `object` - `data` (OrganizationSilence) - `org_id` (string) - `silenced_until` (string (date-time) | null): Alerts are muted until this time. `null` or past means not muted. - `updated_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/silence' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/set-organization-silence.md # Silence all alerts `PUT https://evohub.io/api/v1/silence` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `setOrganizationSilence`. Mutes every Uptime alert of the organization for `minutes` minutes from now. Checks keep running and outages are still recorded; only notifications stop. Sending it again replaces the end time. Requires the `uptime:silence:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `minutes` (integer, required, min 1): How long to stay silent, in minutes. ## Responses ### 200 — The organization's silence. Content type: `application/json` Type: `object` - `data` (OrganizationSilence) - `org_id` (string) - `silenced_until` (string (date-time) | null): Alerts are muted until this time. `null` or past means not muted. - `updated_at` (string (date-time)) ### 400 — The body is not JSON (`INVALID_JSON`), or `minutes` is missing or not above 0 (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/silence' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "minutes": 60 }' ``` --- Source: https://docs.evohub.io/uptime/clear-organization-silence.md # End the silence `DELETE https://evohub.io/api/v1/silence` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `clearOrganizationSilence`. Lifts the organization's silence now. Answers with the cleared state. Requires the `uptime:silence:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The organization's silence. Content type: `application/json` Type: `object` - `data` (OrganizationSilence) - `org_id` (string) - `silenced_until` (string (date-time) | null): Alerts are muted until this time. `null` or past means not muted. - `updated_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/silence' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/get-monitor-silence.md # Get a monitor's silence window `GET https://evohub.io/api/v1/monitors/{id}/silence` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `getMonitorSilence`. The monitor's silence window, or `null` when it has none. A window may be in the future or already over; the monitor is silent only between `starts_at` and `ends_at`. Requires the `uptime:silence:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. ## Responses ### 200 — The window, or `null`. Content type: `application/json` Type: `object` - `data` (MonitorSilence | null) - One of: - MonitorSilence - `monitor_id` (string) - `org_id` (string) - `starts_at` (string (date-time)) - `ends_at` (string (date-time)) - `created_at` (string (date-time)) - null ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist, or belongs to a team the caller cannot see (`MONITOR_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19/silence' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/set-monitor-silence.md # Silence a monitor for a window `PUT https://evohub.io/api/v1/monitors/{id}/silence` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `setMonitorSilence`. Mutes the monitor's alerts from `starts_at` until `ends_at`, for planned work that takes it down on purpose. A monitor has one window; setting a new one replaces it. Requires the `uptime:silence:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. ## Request body (required) Content type: `application/json` Type: `object` - `starts_at` (string (date-time), required) - `ends_at` (string (date-time), required): Must be after `starts_at`. ## Responses ### 200 — The window as saved. Content type: `application/json` Type: `object` - `data` (MonitorSilence) - `monitor_id` (string) - `org_id` (string) - `starts_at` (string (date-time)) - `ends_at` (string (date-time)) - `created_at` (string (date-time)) ### 400 — The body is not valid JSON or a time is not RFC 3339 (`INVALID_BODY`), or a time is missing or `ends_at` is not after `starts_at` (`VALIDATION`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist, or belongs to a team the caller cannot see (`MONITOR_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19/silence' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "ends_at": "2026-10-13T00:00:00Z", "starts_at": "2026-10-12T22:00:00Z" }' ``` --- Source: https://docs.evohub.io/uptime/clear-monitor-silence.md # Clear a monitor's silence window `DELETE https://evohub.io/api/v1/monitors/{id}/silence` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `clearMonitorSilence`. Removes the monitor's silence window, so its alerts are sent again. Succeeds when there was none. Requires the `uptime:silence:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. ## Responses ### 204 — The window was removed. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist, or belongs to a team the caller cannot see (`MONITOR_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19/silence' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/list-notification-channels.md # List notification channels `GET https://evohub.io/api/v1/notification-channels` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `listNotificationChannels`. The organization's notification channels. A channel's webhook URL and secret are never returned; see `NotificationChannel`. Requires the `uptime:channel:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The channels; an empty list when there are none. Content type: `application/json` Type: `object` - `data` (array of NotificationChannel) - `id` (string): Starts with `nch_`. - `org_id` (string) - `name` (string) - `type` (string, one of `chat`, `webhook`, `slack`) - `config` (object) - `url_host` (string) - `url_hint` (string) - `has_secret` (boolean) - `chat_channel_id` (string) - `chat_channel_name` (string) - `chat_channel_kind` (string) - Other keys: string - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/notification-channels' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/create-notification-channel.md # Create a notification channel `POST https://evohub.io/api/v1/notification-channels` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `createNotificationChannel`. Creates a channel. Link it to monitors afterwards with `POST /api/v1/monitors/{id}/channels/{chId}`. | `type` | `config` | | --- | --- | | `chat` | `chat_channel_id`: one of the IDs from `GET /api/v1/notification-channels/chat-apps`. EvoHub stores the channel's ID, name and app; any other key is ignored. | | `webhook` | `url`: the address to POST to. `secret`: optional, sent back in the `X-Webhook-Secret` header. | | `slack` | `url`: a Slack incoming webhook URL. Kept for existing channels; use `chat` for new Slack channels. | A webhook receives a JSON body with `event` (`triggered` or `resolved`), `monitor_id`, `monitor_name`, `monitor_url`, `cause`, `timestamp` and, on `resolved`, `down_duration_seconds`. Any other `type`, `email` included, is refused with 400 `VALIDATION_ERROR`. The answer shows the channel as every later read will: the URL and secret are not returned. Requires the `uptime:channel:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `CreateChannelRequest` - `name` (string, required, min length 1, max length 255) - `type` (string, required, one of `chat`, `webhook`, `slack`) - `config` (object): Depends on `type`; see the table above. - Other keys: string ## Responses ### 201 — The channel was created. Content type: `application/json` Type: `object` - `data` (NotificationChannel) - `id` (string): Starts with `nch_`. - `org_id` (string) - `name` (string) - `type` (string, one of `chat`, `webhook`, `slack`) - `config` (object) - `url_host` (string) - `url_hint` (string) - `has_secret` (boolean) - `chat_channel_id` (string) - `chat_channel_name` (string) - `chat_channel_kind` (string) - Other keys: string - `created_at` (string (date-time)) ### 400 — The body is not JSON (`INVALID_JSON`); `name` or `type` is missing or invalid, or a `chat` channel has no `config.chat_channel_id` (`VALIDATION_ERROR`); or the chat channel is not one of the organization's (`CHAT_CHANNEL_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 409 — Chat apps are not available, so a `chat` channel cannot be created (`CHAT_NOT_AVAILABLE`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 502 — The organization's chat apps could not be read right now (`CHAT_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/notification-channels' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "name": "#ops-alerts", "type": "chat", "config": { "chat_channel_id": "slack:C04ABCDEF12" } }' ``` --- Source: https://docs.evohub.io/uptime/list-chat-app-channels.md # List chat app channels `GET https://evohub.io/api/v1/notification-channels/chat-apps` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `listChatAppChannels`. The Slack and other chat app channels connected to the organization (Organization → Chat apps) that a `chat` notification channel can post to. An empty list when none are connected. Requires the `uptime:channel:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The channels. Content type: `application/json` Type: `object` - `data` (array of ChatAppChannel) - `id` (string, required): Use as `config.chat_channel_id`. - `kind` (string, required): The chat app, e.g. `slack`. - `name` (string, required) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 502 — The organization's chat apps could not be read right now (`CHAT_UNAVAILABLE`). Retry shortly. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/notification-channels/chat-apps' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/delete-notification-channel.md # Delete a notification channel `DELETE https://evohub.io/api/v1/notification-channels/{id}` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `deleteNotificationChannel`. Deletes the channel and unlinks it from every monitor. Requires the `uptime:channel:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `nch_6a9d2f4b-8c1e-4b3a-9f7d-5e0c2a8b4d16`): The channel's ID. ## Responses ### 204 — The channel was deleted. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The channel does not exist in this organization (`CHANNEL_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/notification-channels/nch_6a9d2f4b-8c1e-4b3a-9f7d-5e0c2a8b4d16' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/list-monitor-channels.md # List a monitor's channels `GET https://evohub.io/api/v1/monitors/{id}/channels` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `listMonitorChannels`. The notification channels linked to the monitor, masked as in the channel list. Requires the `uptime:channel:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. ## Responses ### 200 — The linked channels; an empty list when there are none. Content type: `application/json` Type: `object` - `data` (array of NotificationChannel) - `id` (string): Starts with `nch_`. - `org_id` (string) - `name` (string) - `type` (string, one of `chat`, `webhook`, `slack`) - `config` (object) - `url_host` (string) - `url_hint` (string) - `has_secret` (boolean) - `chat_channel_id` (string) - `chat_channel_name` (string) - `chat_channel_kind` (string) - Other keys: string - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist, or belongs to a team the caller cannot see (`MONITOR_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19/channels' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/link-channel-to-monitor.md # Link a channel to a monitor `POST https://evohub.io/api/v1/monitors/{id}/channels/{chId}` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `linkChannelToMonitor`. Sends the monitor's alerts to the channel as well. Linking a channel that is already linked succeeds. Requires the `uptime:channel:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. - `chId` (string, required, example `nch_6a9d2f4b-8c1e-4b3a-9f7d-5e0c2a8b4d16`): The notification channel's ID. ## Responses ### 204 — The channel is linked. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor (`MONITOR_NOT_FOUND`) or the channel (`CHANNEL_NOT_FOUND`) does not exist or is not visible to the caller. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19/channels/nch_6a9d2f4b-8c1e-4b3a-9f7d-5e0c2a8b4d16' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/unlink-channel-from-monitor.md # Unlink a channel from a monitor `DELETE https://evohub.io/api/v1/monitors/{id}/channels/{chId}` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `unlinkChannelFromMonitor`. Stops sending the monitor's alerts to the channel. The channel itself is kept. Requires the `uptime:channel:write` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Path parameters - `id` (string, required, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The monitor's ID. - `chId` (string, required, example `nch_6a9d2f4b-8c1e-4b3a-9f7d-5e0c2a8b4d16`): The notification channel's ID. ## Responses ### 204 — The channel is unlinked. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 404 — The monitor does not exist or is not visible to the caller (`MONITOR_NOT_FOUND`), or the channel is not linked to it (`CHANNEL_NOT_FOUND`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X DELETE 'https://evohub.io/api/v1/monitors/mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19/channels/nch_6a9d2f4b-8c1e-4b3a-9f7d-5e0c2a8b4d16' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/get-weekly-summary-settings.md # Get the weekly summary settings `GET https://evohub.io/api/v1/uptime-digest` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `getWeeklySummarySettings`. Whether the organization's weekly uptime summary email is on, and whether the caller receives it. Requires the `uptime:monitor:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The weekly summary settings, as the caller sees them. Content type: `application/json` Type: `object` - `data` (DigestSettings) - `available` (boolean, required): Whether EvoHub sends the weekly summary at all. When `false`, nothing is sent whatever the other fields say. - `enabled` (boolean, required): Whether the organization has it on. - `subscribed` (boolean, required): Whether the caller receives it, if they are an owner or administrator. - `can_manage` (boolean, required): Whether the caller may turn it on or off and send a preview. Always `false` for an API key. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/uptime-digest' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/set-weekly-summary-enabled.md # Turn the weekly summary on or off `PUT https://evohub.io/api/v1/uptime-digest` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `setWeeklySummaryEnabled`. Turns the weekly summary on or off for the whole organization. Only an owner or administrator signed in to EvoHub can do this. An API key always gets **403**, whatever its scopes or role. Requires the `uptime:monitor:read` scope, and a person who is an owner or administrator. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `enabled` (boolean, required) ## Responses ### 200 — The weekly summary settings, as the caller sees them. Content type: `application/json` Type: `object` - `data` (DigestSettings) - `available` (boolean, required): Whether EvoHub sends the weekly summary at all. When `false`, nothing is sent whatever the other fields say. - `enabled` (boolean, required): Whether the organization has it on. - `subscribed` (boolean, required): Whether the caller receives it, if they are an owner or administrator. - `can_manage` (boolean, required): Whether the caller may turn it on or off and send a preview. Always `false` for an API key. ### 400 — The body is not `{"enabled": true|false}` (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The caller is not an owner or administrator signed in as a person, or lacks `uptime:monitor:read` (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/uptime-digest' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "enabled": true }' ``` --- Source: https://docs.evohub.io/uptime/set-my-weekly-summary-subscription.md # Subscribe or unsubscribe yourself `PUT https://evohub.io/api/v1/uptime-digest/me` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `setMyWeeklySummarySubscription`. The caller's own choice to receive the weekly summary — the same as the unsubscribe link in the email. With a personal API key, it is the key's owner who is subscribed or unsubscribed. Requires the `uptime:monitor:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Request body (required) Content type: `application/json` Type: `object` - `subscribed` (boolean, required) ## Responses ### 200 — The weekly summary settings, as the caller sees them. Content type: `application/json` Type: `object` - `data` (DigestSettings) - `available` (boolean, required): Whether EvoHub sends the weekly summary at all. When `false`, nothing is sent whatever the other fields say. - `enabled` (boolean, required): Whether the organization has it on. - `subscribed` (boolean, required): Whether the caller receives it, if they are an owner or administrator. - `can_manage` (boolean, required): Whether the caller may turn it on or off and send a preview. Always `false` for an API key. ### 400 — The body is not `{"subscribed": true|false}` (`VALIDATION_ERROR`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X PUT 'https://evohub.io/api/v1/uptime-digest/me' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "subscribed": false }' ``` --- Source: https://docs.evohub.io/uptime/send-weekly-summary-preview.md # Email yourself a preview `POST https://evohub.io/api/v1/uptime-digest/preview` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `sendWeeklySummaryPreview`. Emails the caller the summary of the last full week. Takes no body. Only an owner or administrator signed in to EvoHub can do this, at most once a minute. An API key always gets **403**. Requires the `uptime:monitor:read` scope, and a person who is an owner or administrator. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Responses ### 200 — The preview was sent. Content type: `application/json` Type: `object` - `data` (object) - `sent_to` (string, required): The address it was sent to. ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The caller is not an owner or administrator signed in as a person, or lacks `uptime:monitor:read` (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 409 — The weekly summary email is not available yet (`DIGEST_UNAVAILABLE`), there are no monitors to summarize (`NO_MONITORS`), or the caller's account has no address it can be sent to (`NO_DELIVERABLE_EMAIL`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — A preview was sent to the caller less than a minute ago (`RATE_LIMITED`), or the API rate limit was reached. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 502 — The email provider refused the preview (`EMAIL_REJECTED`), or it could not be sent (`PREVIEW_FAILED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X POST 'https://evohub.io/api/v1/uptime-digest/preview' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/list-uptime-audit-logs.md # List Uptime audit log entries `GET https://evohub.io/api/v1/uptime-audit-logs` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `listUptimeAuditLogs`. Changes made in Uptime, newest first: monitors created, changed, paused, resumed, deleted, silenced and linked to channels; channels created and deleted; the organization's silence set and cleared; the weekly summary turned on or off. Filter to one resource with `resource_type` and `resource_id` together; either alone is ignored. `data` is `[]` when there are no entries. Requires the `uptime:audit:read` scope. ## Authorization Any one of: - `bearerKey` - `headerKey` Where: - `bearerKey`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token. - `headerKey`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`). ## Query parameters - `resource_type` (string, example `monitor`): `monitor`, `channel`, `silence` or `weekly_digest`. Needs `resource_id`. - `resource_id` (string, example `mon_8c1d0b52-6f3e-4a7d-9b21-0e5c4f7a3d19`): The resource's ID. Needs `resource_type`. - `limit` (integer, default `100`, min 1, max 500): How many entries to return. A value outside 1–500, or not a number, is read as 100. ## Responses ### 200 — The entries. Content type: `application/json` Type: `object` - `data` (array of AuditLogEntry) - `id` (string) - `org_id` (string) - `user_id` (string): Who made the change; for an organization API key, the key's ID. - `resource_type` (string, one of `monitor`, `channel`, `silence`, `weekly_digest`) - `resource_id` (string) - `action` (string): `monitor.created`, `monitor.updated`, `monitor.paused`, `monitor.resumed`, `monitor.deleted`, `monitor.silenced`, `monitor.silence_cleared`, `monitor.channel_linked`, `monitor.channel_unlinked`, `channel.created`, `channel.deleted`, `silence.set`, `silence.cleared`, or `update` (weekly summary). - `actor_type` (string, value `user`) - `details` (object) - Other keys: any - `created_at` (string (date-time)) ### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`). Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 429 — Too many requests (`RATE_LIMITED`). Wait for `Retry-After` seconds. Headers: - `Retry-After` (integer): Seconds to wait before retrying. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later. Content type: `application/json` Type: `Error` - `error` (object, required) - `code` (string, required, example `MONITOR_NOT_FOUND`) - `message` (string, required, example `monitor not found`) - `request_id` (string) ## Example request ```bash curl -X GET 'https://evohub.io/api/v1/uptime-audit-logs' \ -H 'Authorization: Bearer ' ``` --- Source: https://docs.evohub.io/uptime/ping-heartbeat.md # Ping a heartbeat monitor `GET https://evohub.io/ping/{token}` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `pingHeartbeat`. Records a ping for the heartbeat monitor with this token. The monitor goes down when no ping arrives within its `interval_seconds` plus `grace_seconds`, and comes back up on the next ping. No API key: the token is the only credential, so keep the URL out of public repositories and logs. GET, POST and HEAD all work, and a POST body is ignored. Pings are not counted against the API rate limit. ## Path parameters - `token` (string, required, example `hb_9e1f3a5c-7b2d-4c6e-8f0a-1b3d5e7f9a2c`): The heartbeat monitor's `ping_token`. ## Responses ### 200 — The ping was recorded. Content type: `application/json` Type: `object` - `status` (string, required, value `ok`) ### 404 — No heartbeat monitor has this token. The body is plain text. Content type: `text/plain` Type: `string` ### 500 — The ping could not be recorded. Retry. The body is plain text. Content type: `text/plain` Type: `string` ## Example request ```bash curl -X GET 'https://evohub.io/ping/hb_9e1f3a5c-7b2d-4c6e-8f0a-1b3d5e7f9a2c' ``` --- Source: https://docs.evohub.io/uptime/ping-heartbeat-post.md # Ping a heartbeat monitor (POST) `POST https://evohub.io/ping/{token}` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `pingHeartbeatPost`. The same as the GET form. A body is ignored. ## Path parameters - `token` (string, required, example `hb_9e1f3a5c-7b2d-4c6e-8f0a-1b3d5e7f9a2c`): The heartbeat monitor's `ping_token`. ## Responses ### 200 — The ping was recorded. Content type: `application/json` Type: `object` - `status` (string, required, value `ok`) ### 404 — No heartbeat monitor has this token. The body is plain text. Content type: `text/plain` Type: `string` ### 500 — The ping could not be recorded. Retry. The body is plain text. Content type: `text/plain` Type: `string` ## Example request ```bash curl -X POST 'https://evohub.io/ping/hb_9e1f3a5c-7b2d-4c6e-8f0a-1b3d5e7f9a2c' ``` --- Source: https://docs.evohub.io/uptime/ping-heartbeat-head.md # Ping a heartbeat monitor (HEAD) `HEAD https://evohub.io/ping/{token}` Part of the [Uptime API](https://docs.evohub.io/uptime.md) reference · operationId `pingHeartbeatHead`. The same as the GET form, without a response body. ## Path parameters - `token` (string, required, example `hb_9e1f3a5c-7b2d-4c6e-8f0a-1b3d5e7f9a2c`): The heartbeat monitor's `ping_token`. ## Responses ### 200 — The ping was recorded. ### 404 — No heartbeat monitor has this token. ### 500 — The ping could not be recorded. Retry. ## Example request ```bash curl -X HEAD 'https://evohub.io/ping/hb_9e1f3a5c-7b2d-4c6e-8f0a-1b3d5e7f9a2c' ```