Docs/Integrations

The Oh Dear API

The Oh Dear API lets you configure everything about our application through a simple, structured Application Programming Interface (API).

Everything you see in your dashboard can be controlled with the API. And as a bonus, all changes you make with the API will be visible in realtime on your dashboard.

Oh Dear dashboard

Notice

On 23rd August 2025, we've made breaking changes to our API. You'll find more info on why we made these changes and how to upgrade on this page.

Getting started

Let's help get you started.

  1. Learn about our API authentication and generate your first API key
  2. Retrieve a list of all your monitors and their checks with the API.
  3. Configure your status pages automatically.

Optionally, we provide a PHP SDK package to help get you started and a list of data types we use in our API.

API endpoints

All Oh Dear API endpoints are located at https://ohdear.app/api. From there on, you will find a logical structure that follows to the REST standard.

Here's a quick summary of the API methods.

  • GET: all GET requests are for data retrieval only (site listing, account info, ...) and will never modify any data.
  • POST: a POST method will add new monitors or checks to Oh Dear
  • DELETE: the DELETE method is used to delete certain monitors or checks from your account.
  • PUT: this method is used to update information on existing monitors, checks or your account.

In general, GET requests can be performed as many times as you'd like (they are idempotent), all other methods actually transform data in your account and will cause changes to take effect.

Response data

All responses from the Oh Dear API will be formatted as JSON.

Here's an example payload of the /api/monitors endpoint, that lists all monitors.

{
  "data": [
    {
      "id": 1,
      "url": "https://yoursite.tld",
      ...
    },
    {
      "id": 2,
      "url": "https://yourothersite.tld",
      ...
    }
  ]
}

Each endpoint will return specific data for that request. More info on that in the monitors and checks pages.

Get your API token

Once you are logged in to your dashboard navigate to API access and create your API token.

You can name your token, so you know where this one is going to be used, in case you ever need to revoke it.

Oh Dear API access

Once you create a token, it'll be shown only once to you. Make sure to store it safely, after this it can only be revoked and you can generate a new token for use.

Once you have your token, you can authenticate against the application.

By default, tokens can perform any actions to everything in your Oh Dear account. You can also create API tokens that only can be used in API calls to a selection of monitors and status pages.

Oh Dear API scoped_token

Authenticate against the API

The token you've received can be used as the Authorization header.

Here's a curl example of how you can authenticate against the API. In this example, it'll list all monitors in your account.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

The token bgUKSWYL30iHg5w0WTDGHfubt5L1HBTr0atAehCeSqwNTqkU9rOmsNEmWf6Y is used to authenticate. The /api/monitors endpoint is hit to retrieve all monitors. The resulting response payload may look like this.

{
  "data": [
    {
      "id": 1,
      "url": "https://yoursite.tld",
      ...
    },
    {
      "id": 2,
      "url": "https://yourothersite.tld",
      ...
    }
  ]
}

The actual payload is usually much bigger, but that's described in more detail in the monitors and checks page.

Data types

Check Types

  • uptime
  • performance
  • broken_links
  • mixed_content
  • lighthouse
  • cron
  • application_health
  • sitemap
  • dns
  • domain
  • certificate_health
  • link
  • image
  • script
  • stylesheet
  • og:image

Check Results

  • pending
  • succeeded
  • warning
  • failed
  • errored-or-timed-out

Summarized Check Results

  • succeeded
  • warning
  • failed

Crawler/Sitemap Speed

  • slowest
  • slow
  • default
  • fast
  • fastest

Lighthouse Slowdown Modifier

  • 0 (fastest) to
  • 5 (slowest)

Lighthouse continent

  • europe
  • north-america
  • asia

Response Header Conditions

  • contains
  • not contains
  • equals
  • matches pattern

Uptime Check Locations

Africa

  • cape town

Asia

  • bangalore
  • seoul
  • singapore
  • tokyo

Australia

  • sydney

Canada

  • toronto

Europe

  • frankfurt
  • london
  • paris
  • stockholm

Middle East

  • bahrain

South America

  • sao paulo

US East

  • new york

US Mid

  • dallas

US West

  • los angeles
  • san francisco

DNS Record Types

  • A
  • AAAA
  • CAA
  • CNAME
  • MX
  • NS
  • SOA
  • SRV
  • TXT
  • PTR

Uptime check HTTP Verb

  • get
  • post
  • patch
  • put

Monitors

Get all monitors in your account

The /api/monitors endpoint lists all your monitors in your account.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

As a result, you get the details for every site you've added.

{
    "data": [
        {
            "id": 1,
            "team_id": 1,
            "type": "http",
            "url": "https://freek.dev",
            "uses_https": true,
            "sort_url": "freek.dev",
            "label": "freek.dev",
            "group_name": "",
            "tags": [],
            "description": null,
            "notes": null,
            "latest_run_date": "2019-09-16 07:29:02",
            "summarized_check_result": "succeeded",
            "checks": [
                {
                    "id": 100,
                    "type": "uptime",
                    "label": "Uptime",
                    "enabled": true,
                    "latest_run_ended_at": "2019-09-16 07:29:02",
                    "latest_run_result": "succeeded",
                    "summary": "Up",
                    "settings": {
                        "uptime_check_location": "paris",
                        "uptime_check_expected_final_redirect_url": null,
                        "uptime_check_failed_notification_threshold": null,
                        "uptime_check_http_verb": null,
                        "uptime_check_timeout": null,
                        "uptime_check_max_redirect_count": null,
                        "uptime_check_payload": null,
                        "uptime_check_valid_status_codes": null,
                        "uptime_check_look_for_string": null,
                        "uptime_check_absent_string": null,
                        "uptime_check_expected_response_headers": null,
                        "http_client_headers": null
                    },
                    "active_snooze": null
                },
                {
                    "id": 101,
                    "type": "broken_links",
                    "label": "Broken links",
                    "enabled": true,
                    "latest_run_ended_at": "2019-09-16 07:29:05",
                    "latest_run_result": "succeeded",
                    "summary": "None",
                    "settings": {
                        "broken_links_check_include_external_links": null,
                        "broken_links_whitelisted_urls": null,
                        "crawler_headers": [],
                        "crawler_speed": "default",
                        "broken_link_types": null,
                        "respect_robots": true
                    },
                    "active_snooze": null
                }
            ],
            "uptime_check_settings": {
                "location": "paris",
                "look_for_string": "",
                "absent_string": null,
                "expected_response_headers": [],
                "failed_notification_threshold": 2,
                "http_verb": "get",
                "payload": [],
                "timeout": 5,
                "valid_status_codes": ["2*"],
                "max_redirect_count": 5,
                "expected_final_redirect_url": null,
                "http_client_headers": []
            },
            "certificate_health_check_settings": {
                "expires_soon_threshold_in_days": null
            },
            "broken_links_check_settings": {
                "whitelisted_urls": [],
                "check_include_external_links": false,
                "types": ["link", "image", "script", "stylesheet", "og:image"],
                "do_not_crawl_urls": [],
                "force_crawl_urls": [],
                "new_links_only": false
            },
            "dns_check_settings": {
                "extra_cnames": [],
                "monitor_main_domain": false,
                "ignored_record_types": ["SOA"],
                "check_nameservers_in_sync": true
            },
            "lighthouse_check_settings": {
                "continent": "europe",
                "cpu_slowdown_modifier": 0,
                "notification_settings": null,
                "preferred_server_name": "lighthouse-checker-frankfurt-1",
                "http_client_headers": []
            },
            "application_health_check_settings": {
                "result_url": null,
                "secret": "your-secret-key",
                "headers": []
            },
            "domain_check_settings": {
                "not_supported_reason": null,
                "expires_soon_threshold_in_days": null
            },
            "performance_check_settings": {
                "threshold_in_ms": 3500,
                "change_percentage": 50
            },
            "sitemap_check_settings": {
                "path": "sitemap.xml",
                "speed": "default"
            },
            "crawler_headers": [],
            "send_report_to_emails": [],
            "include_check_types_in_report": [],
            "badge_id": "badge-123",
            "marked_for_deletion_at": null,
            "created_at": "2019-09-16 07:25:00",
            "updated_at": "2019-09-16 07:25:00"
        },
        {
            "id": 2,
            "team_id": 1,
            "type": "ping",
            "url": "spatie.be",
            "uses_https": false,
            "sort_url": "spatie.be",
            "label": "spatie.be",
            "group_name": "",
            "tags": [],
            "description": null,
            "notes": null,
            "latest_run_date": "2019-09-16 07:30:00",
            "summarized_check_result": "succeeded",
            "checks": [
                {
                    "id": 200,
                    "type": "uptime",
                    "label": "Uptime",
                    "enabled": true,
                    "latest_run_ended_at": "2019-09-16 07:30:00",
                    "latest_run_result": "succeeded",
                    "summary": "Up",
                    "settings": {
                        "uptime_check_location": "paris",
                        "uptime_check_expected_final_redirect_url": null,
                        "uptime_check_failed_notification_threshold": null,
                        "uptime_check_http_verb": null,
                        "uptime_check_timeout": null,
                        "uptime_check_max_redirect_count": null,
                        "uptime_check_payload": null,
                        "uptime_check_valid_status_codes": null,
                        "uptime_check_look_for_string": null,
                        "uptime_check_absent_string": null,
                        "uptime_check_expected_response_headers": null,
                        "http_client_headers": null
                    },
                    "active_snooze": null
                }
            ],
            "uptime_check_settings": {
                "location": "paris",
                "ttl": 64,
                "count": 5,
                "packet_size_in_bytes": 56,
                "acceptable_packet_loss_percentage": 0,
                "acceptable_average_response_time_in_ms": 400,
                "timeout_in_seconds": 1,
                "interval_in_seconds": 1,
                "failed_notification_threshold": 2
            },
            "badge_id": "badge-456",
            "created_at": "2019-09-16 07:26:00",
            "updated_at": "2019-09-16 07:26:00"
        },
        {
            "id": 3,
            "team_id": 1,
            "type": "tcp",
            "url": "smtp.gmail.com:3306",
            "uses_https": false,
            "sort_url": "smtp.gmail.com:3306",
            "label": "smtp.gmail.com:3306",
            "group_name": "",
            "tags": [],
            "description": null,
            "notes": null,
            "latest_run_date": "2019-09-16 07:31:00",
            "summarized_check_result": "succeeded",
            "checks": [
                {
                    "id": 300,
                    "type": "uptime",
                    "label": "Uptime",
                    "enabled": true,
                    "latest_run_ended_at": "2019-09-16 07:31:00",
                    "latest_run_result": "succeeded",
                    "summary": "Up",
                    "settings": {
                        "uptime_check_location": "paris",
                        "uptime_check_expected_final_redirect_url": null,
                        "uptime_check_failed_notification_threshold": null,
                        "uptime_check_http_verb": null,
                        "uptime_check_timeout": null,
                        "uptime_check_max_redirect_count": null,
                        "uptime_check_payload": null,
                        "uptime_check_valid_status_codes": null,
                        "uptime_check_look_for_string": null,
                        "uptime_check_absent_string": null,
                        "uptime_check_expected_response_headers": null,
                        "http_client_headers": null
                    },
                    "active_snooze": null
                }
            ],
            "uptime_check_settings": {
                "location": "paris",
                "tcp_send_string": "",
                "timeout_in_ms": 1000,
                "look_for_string_in_welcome_message": "",
                "look_for_string_in_send_string_response": "",
                "failed_notification_threshold": 2
            },
            "badge_id": "badge-789",
            "created_at": "2019-09-16 07:27:00",
            "updated_at": "2019-09-16 07:27:00"
        }
    ],
    "links": {
        "first": "https://ohdear.app/api/monitors?page%5Bnumber%5D=1",
        "last": "https://ohdear.app/api/monitors?page%5Bnumber%5D=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "path": "https://ohdear.app/api/monitors",
        "per_page": 200,
        "to": 3,
        "total": 3
    }
}

Let's drill this down. The three monitors shown represent different monitor types:

  1. HTTP Monitor (type: "http"): This is an https:// website monitor that gets comprehensive monitoring including uptime, performance, certificate health, broken links, mixed content, lighthouse, DNS, domain expiration, etc.

  2. Ping Monitor (type: "ping"): This monitors server availability using ICMP ping. It only has uptime checks and uses different settings (TTL, packet size, packet loss percentage, etc.).

  3. TCP Port Monitor (type: "tcp"): This monitors specific TCP port availability (like MySQL, SMTP, etc.). It has uptime checks with TCP-specific settings like connection strings and timeouts.

Each monitor type has different available checks and settings based on what can be monitored. More on each check in our dedicated checks page.

Also notice that the content of the uptime_check_settings depends on the type of monitor.

Get a specific monitor via the API

If you don't want to retrieve all monitors, you can get a specific monitor if you know its ID. The example below will get the details of monitor ID 99.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors/99 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

The resulting response is a single monitor object:

{
  "id": 99,
  "team_id": 1,
  "type": "http",
  "url": "https://example.com",
  "uses_https": true,
  "sort_url": "example.com",
  "label": "example.com",
  "group_name": "",
  "tags": [],
  "description": null,
  "notes": null,
  "latest_run_date": "2019-09-16 07:29:02",
  "summarized_check_result": "succeeded",
  "checks": [
    {
      "id": 100,
      "type": "uptime",
      "label": "Uptime",
      "enabled": true,
      "latest_run_ended_at": "2019-09-16 07:29:02",
      "latest_run_result": "succeeded",
      "summary": "Up",
      "settings": {
        "uptime_check_location": "paris"
      },
      "active_snooze": null
    }
  ],
  "uptime_check_settings": {
    "location": "paris",
    "look_for_string": "",
    "absent_string": null,
    "expected_response_headers": [],
    "failed_notification_threshold": 2,
    "http_verb": "get",
    "payload": [],
    "timeout": 5,
    "valid_status_codes": ["2*"],
    "max_redirect_count": 5,
    "expected_final_redirect_url": null,
    "http_client_headers": []
  },
  "badge_id": "badge-123",
  "marked_for_deletion_at": null,
  "created_at": "2019-09-16 07:25:00",
  "updated_at": "2019-09-16 07:25:00"
}

Notice how this payload does not contain the data wrapper (the API call to retrieve all monitors does).

Monitor Properties

The response gives you comprehensive details for each monitor. Here are the properties:

Core Properties

  • id: Unique internal identifier for the monitor
  • team_id: Every monitor belongs to a Team. This is the team's ID. Since you can belong to multiple teams, the corresponding team ID will be shown for every monitor.
  • type: The monitor type - "http" for websites, "ping" for ICMP ping monitoring, or "tcp" for TCP port monitoring
  • url: The URL/hostname you submitted to Oh Dear (includes protocol for HTTP monitors, hostname for ping, hostname:port for TCP)
  • uses_https: Boolean indicating whether it uses HTTPS (mainly relevant for HTTP monitors)
  • sort_url: The URL without protocol and www. prefix, used for sorting
  • label: Display name for the monitor (defaults to sort_url if no custom name provided)
  • group_name: Name of the group this monitor belongs to (empty string if ungrouped)
  • tags: Array of tag names associated with this monitor
  • description: Optional description for the monitor
  • notes: Custom notes for the monitor
  • latest_run_date: Timestamp of when any check for this monitor last ran
  • summarized_check_result: Overall monitor health - "succeeded", "warning", "failed", or "pending"

Checks Array

  • checks: Array of all checks configured for this monitor. Each check includes:
    • id: Unique identifier for the check
    • type: Check type (uptime, performance, certificate_health, broken_links, mixed_content, lighthouse, cron, application_health, sitemap, dns, domain)
    • label: Human-readable label for the check
    • enabled: Whether this check is currently active
    • latest_run_ended_at: When this specific check last completed
    • latest_run_result: Result of the last run (succeeded, failed, warning, pending, errored-or-timed-out)
    • summary: Human-readable summary of the current status
    • settings: Check-specific configuration (varies by check type)
    • active_snooze: Information about any active notification snoozing

Monitor Type-Specific Settings

Depending on the monitor type, different settings objects are included:

HTTP Monitors (type: "http"):

  • uptime_check_settings: HTTP-specific uptime settings
    • location: Server location for checks (e.g., "paris")
    • look_for_string: Text that must be present in response
    • absent_string: Text that must NOT be present in response
    • expected_response_headers: Headers that must be present
    • failed_notification_threshold: How many failures before alerting
    • http_verb: HTTP method to use ("get", "post", etc.)
    • payload: Request body for POST/PUT requests
    • timeout: Request timeout in seconds
    • valid_status_codes: Array of acceptable HTTP status codes (e.g., ["2*", "3*"])
    • max_redirect_count: Maximum redirects to follow
    • expected_final_redirect_url: Expected final URL after redirects
    • http_client_headers: Custom headers to send with requests

Ping Monitors (type: "ping"):

  • uptime_check_settings: ICMP ping settings
    • location: Server location for checks
    • ttl: Time-to-live for ping packets
    • count: Number of ping packets to send
    • packet_size_in_bytes: Size of each ping packet
    • acceptable_packet_loss_percentage: Maximum acceptable packet loss
    • acceptable_average_response_time_in_ms: Maximum acceptable average response time
    • timeout_in_seconds: Timeout for each ping
    • interval_in_seconds: Interval between pings
    • failed_notification_threshold: How many failures before alerting

TCP Monitors (type: "tcp"):

  • uptime_check_settings: TCP connection settings
    • location: Server location for checks
    • tcp_send_string: String to send after connecting
    • timeout_in_ms: Connection timeout in milliseconds
    • look_for_string_in_welcome_message: Text to look for in initial server response
    • look_for_string_in_send_string_response: Text to look for in response to sent string
    • failed_notification_threshold: How many failures before alerting

Check-Specific Settings Objects

All monitors include these settings objects (even if the checks are disabled):

  • certificate_health_check_settings: SSL certificate monitoring
    • expires_soon_threshold_in_days: Days before expiration to warn
  • broken_links_check_settings: Broken link detection
    • whitelisted_urls: URLs to ignore if broken
    • check_include_external_links: Whether to check external links
    • types: Types of links to check (["link", "image", "script", "stylesheet", "og:image"])
    • do_not_crawl_urls: URLs to exclude from crawling
    • force_crawl_urls: URLs to always crawl
    • new_links_only: Only check newly discovered links
  • dns_check_settings: DNS monitoring configuration
    • extra_cnames: Additional CNAME records to monitor
    • monitor_main_domain: Whether to monitor the main domain
    • ignored_record_types: DNS record types to ignore (["SOA"])
    • check_nameservers_in_sync: Whether to verify nameservers are synchronized
  • lighthouse_check_settings: Performance audit configuration
    • continent: Geographic region for testing ("europe", "america", etc.)
    • cpu_slowdown_modifier: CPU throttling factor
    • notification_settings: Custom notification thresholds
    • preferred_server_name: Specific server to use for testing
    • http_client_headers: Custom headers for the audit
  • application_health_check_settings: Custom application health endpoint
    • result_url: URL of your health check endpoint
    • secret: Secret token for authentication
    • headers: Custom headers to send
  • domain_check_settings: Domain expiration monitoring
    • not_supported_reason: Why domain monitoring isn't available (if applicable)
    • expires_soon_threshold_in_days: Days before expiration to warn
  • performance_check_settings: Response time monitoring
    • threshold_in_ms: Maximum acceptable response time
    • change_percentage: Percentage change threshold for alerts
  • sitemap_check_settings: Sitemap validation
    • path: Path to sitemap file ("sitemap.xml")
    • speed: Crawl speed ("default", "fast", "slow")

Additional Properties

  • crawler_headers: Custom headers sent during crawling operations
  • send_report_to_emails: Email addresses that receive the monthly reports
  • include_check_types_in_report: Which check types are included in monthly reports
  • badge_id: Unique identifier for status badge integration
  • marked_for_deletion_at: Timestamp if monitor is scheduled for deletion
  • created_at: When the monitor was created
  • updated_at: When the monitor was last modified

Getting a summary of the results of a check

Oh Dear can perform various checks (uptime, mixed content, broken links, ...) for your monitors.

If you want to know the status of a particular check, you can query the /api/monitors/{monitorId}/check-summary/{checkType} endpoint.

The checkType segment can be one of the check types we support.

Here's an example:

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors/99/check-summary/uptime \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

The response holds two properties:

  • result: this is the result of the check. Possible values are pending, succeeded, warning, failed or errored-or-timed-out.
  • summary: a human-readable summary of the check result

Add a monitor through the API

To add a monitor, create a POST call to the /api/monitors/ endpoint. Here's an example to add the monitor https://mybrandnewsite.tld to Oh Dear.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/monitors \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{"url":"https://example.com", "team_id":"1", "type":"http"}'

Remember that we love JSON? Your request payload should also be valid JSON. The minimum actual payload, when formatted, looks like this.

{
    "url": "https://example.com",
    "team_id": "1",
    "type": "http"
}

Mandatory fields are:

  • url: The URL/hostname to monitor
  • team_id: The team where this monitor should be added to
  • type: The monitor type - "http" for websites, "ping" for ICMP ping monitoring, or "tcp" for TCP port monitoring

To find out the teams you belong to, see the user info API call.

If the API call succeeded, you'll be given output like this, showcasing every check that was added:

{
  "id": 173,
  "team_id": 1,
  "type": "http",
  "url": "https://example.com",
  "uses_https": true,
  "sort_url": "example.com",
  "label": "example.com",
  "group_name": "",
  "tags": [],
  "description": null,
  "notes": null,
  "latest_run_date": null,
  "summarized_check_result": "pending",
  "checks": [
    {
      "id": 560,
      "type": "uptime",
      "label": "Uptime",
      "enabled": true,
      "latest_run_ended_at": null,
      "latest_run_result": "pending",
      "summary": "Pending",
      "settings": {
        "uptime_check_location": "paris"
      },
      "active_snooze": null
    },
    {
      "id": 561,
      "type": "certificate_health",
      "label": "Certificate health",
      "enabled": true,
      "latest_run_ended_at": null,
      "latest_run_result": "pending", 
      "summary": "Pending",
      "settings": {
        "certificate_health_check_settings": {
          "expires_soon_threshold_in_days": null
        }
      },
      "active_snooze": null
    }
  ],
  "uptime_check_settings": {
    "location": "paris",
    "look_for_string": "",
    "absent_string": null,
    "expected_response_headers": [],
    "failed_notification_threshold": 2,
    "http_verb": "get",
    "payload": [],
    "timeout": 5,
    "valid_status_codes": ["2*"],
    "max_redirect_count": 5,
    "expected_final_redirect_url": null,
    "http_client_headers": []
  },
  "badge_id": "badge-abc123",
  "marked_for_deletion_at": null,
  "created_at": "2019-09-16 08:00:00",
  "updated_at": "2019-09-16 08:00:00"
}

Add a monitor with enabled checks

When creating a site you can control which checks get enabled. You can do so by passing an array with one of these check types.

Here's an example payload where a site is created with only the uptime and mixed_content checks enabled.

{
    "url": "https://mybrandnewsite.tld",
    "team_id": "1",
    "checks": ["uptime", "mixed_content"]
}

For more details on each check, have a look at the checks API endpoint.

Add a monitor with custom settings

If you need to control the settings of a site, you can pass additional parameters in the payload. Here's an example of a site with all possible settings. You can also control the monthly site report settings in this payload using the send_report_to_emails and include_check_types_in_report settings.

Please be aware:

  • url is required
  • team_id is required and should be a team you belong to
  • checks is required and should be an array of check types
  • application_health_check_secret should not be present if you want to generate a random secret
  • default values will be used for any settings not present in the payload
  • we suggest only adding settings you want to change from the defaults
{
    "url": "https://ohdear.app/docs",
    "team_id": 1,
    "checks": [
      "uptime",
      "performance",
      "broken_links",
      "mixed_content",
      "lighthouse",
      "cron",
      "application_health",
      "sitemap",
      "dns",
      "domain",
      "certificate_health"
    ],
    "group_name": "Internal",
    "friendly_name": "Documentation",
    "tags": ["internal", "production", "docs"],
    "notes": "Internal notes for our team members",
    "description": "Oh Dear documentation",
    "uptime_check_location": "paris",
    "uptime_check_expected_final_redirect_url": "https://redirect-to-this-url.com",
    "uptime_check_failed_notification_threshold": 2,
    "uptime_check_http_verb": "get",
    "uptime_check_timeout": 5,
    "uptime_check_max_redirect_count": 5,
    "uptime_check_payload": [
        {
          "name": "payload-name-1",
          "value": "payload-value-1"
        }
    ],
    "uptime_check_valid_status_codes": ["2*"],
    "uptime_check_look_for_string": null,
    "uptime_check_absent_string": null,
    "uptime_check_expected_response_headers": [
        {
          "name": "my-response-header", 
          "condition": "equals", 
          "value": "my-response-value"
          }
    ],
    "http_client_headers": [
        {
          "name": "my-header",
          "value": "my-value"
        }
    ],
    "performance_threshold_in_ms": 3500,
    "performance_change_percentage": 50,
    "crawler_headers": [
        {
          "name": "my-broken-links-header",
          "value": "my-broken-links-value"
        }
    ],
    "broken_links_check_include_external_links": false,
    "broken_link_types": ["link", "image", "script", "stylesheet", "og:image"],
    "broken_links_whitelisted_urls": [],
    "respect_robots": true,
    "sitemap_path": "/sitemap.xml",
    "sitemap_speed": "slow",
    "application_health_check_result_url": "https://mybrandnewsite.tld/health",
    "application_health_headers": [
        {"name": "my-header", "value": "my-value"}
    ],
    "certificate_health_check_expires_soon_threshold_in_days": 14,
    "dns_check_nameservers_in_sync": true,
    "dns_monitor_main_domain": false,
    "dns_extra_cnames": ["cname1", "cname2"],
    "dns_ignored_record_types": ["A", "CNAME"],
    "domain_check_expires_soon_threshold_in_days": 30,
    "lighthouse_check_continent": "europe",
    "lighthouse_cpu_slowdown_modifier": 0,
    "send_report_to_emails": [
      "[email protected]",
      "[email protected]"
    ],
	  "include_check_types_in_report": [
	    "uptime",
      "performance",
      "broken_links",
      "mixed_content",
      "lighthouse",
      "cron",
      "application_health",
      "sitemap",
      "dns",
      "domain",
      "certificate_health",
	  ]
}

Error handling

If an error occurred, you'll be given a non-HTTP/200 response code. The resulting payload might look like this.

{
    "message": "The given data was invalid.",
    "errors": {
        "url": ["The url field is required."],
        "team_id": ["The team id field is required."]
    }
}

If you've got both the url and the team_id in your payload, but the data is invalid, you'll get a detailed error message.

{
    "message": "The given data was invalid.",
    "errors": {
        "url": ["You should enter a url that starts with either \"http://\" or \"https://\"."],
        "team_id": ["You do not belong to that team."]
    }
}

Updating monitor settings

You can update the monitor settings by performing a PUT request to the /api/monitors/{monitorId} endpoint.

The payload is the same as creating a monitor wit the following differences:

  • not all keys are required (only the ones you want to update)
  • url is not required (but can be updated if needed)
  • team_id is not allowed as a monitor cannot be moved between teams
{
    "url": "https://example.com/docs",
    "type": "http",
    "checks": [
      "uptime",
      "performance",
      "broken_links",
      "mixed_content",
      "lighthouse", 
      "cron",
      "application_health",
      "sitemap",
      "dns",
      "domain",
      "certificate_health"
    ],
    "group_name": "Internal",
    "label": "Documentation",
    "tags": ["internal", "production", "docs"],
    "notes": "Internal notes for our team members",
    "description": "Documentation site monitoring",
    "uptime_check_settings": {
        "location": "paris",
        "expected_final_redirect_url": "https://redirect-to-this-url.com",
        "failed_notification_threshold": 2,
        "http_verb": "get",
        "timeout": 5,
        "max_redirect_count": 5,
        "payload": [
            {
                "name": "payload-name-1",
                "value": "payload-value-1"
            }
        ],
        "valid_status_codes": ["2*"],
        "look_for_string": null,
        "absent_string": null,
        "expected_response_headers": [
            {
                "name": "my-response-header",
                "condition": "equals", 
                "value": "my-response-value"
            }
        ],
        "http_client_headers": [
            {
                "name": "my-header",
                "value": "my-value"
            }
        ]
    },
    "performance_check_settings": {
        "threshold_in_ms": 3500,
        "change_percentage": 50
    },
    "broken_links_check_settings": {
        "check_include_external_links": false,
        "types": ["link", "image", "script", "stylesheet", "og:image"],
        "whitelisted_urls": [],
        "respect_robots": true
    },
    "sitemap_check_settings": {
        "path": "/sitemap.xml",
        "speed": "slow"
    },
    "application_health_check_settings": {
        "result_url": "https://example.com/health",
        "secret": null,
        "headers": [
            {
                "name": "my-header",
                "value": "my-value"
            }
        ]
    },
    "certificate_health_check_settings": {
        "expires_soon_threshold_in_days": 14
    },
    "dns_check_settings": {
        "check_nameservers_in_sync": true,
        "monitor_main_domain": false,
        "extra_cnames": ["cname1", "cname2"],
        "ignored_record_types": ["A", "CNAME"]
    },
    "domain_check_settings": {
        "expires_soon_threshold_in_days": 30
    },
    "lighthouse_check_settings": {
        "continent": "europe",
        "cpu_slowdown_modifier": 0
    },
    "crawler_headers": [
        {
            "name": "my-broken-links-header",
            "value": "my-broken-links-value"
        }
    ],
    "send_report_to_emails": [
        "[email protected]",
        "[email protected]"
    ],
    "include_check_types_in_report": [
        "uptime",
        "performance",
        "broken_links",
        "mixed_content",
        "lighthouse",
        "cron",
        "application_health",
        "sitemap",
        "dns",
        "domain",
        "certificate_health"
    ]
}

Deleting a monitor

Use the DELETE method to delete a monitor through the API. In this example, we'll delete the monitor with ID 1.

$ OHDEAR_TOKEN="your API token"
$ curl -X DELETE https://ohdear.app/api/monitors/1 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

Notice how we're calling the DELETE method on the /api/monitors/1 endpoint. The 1 in the URL determines which monitor to delete.

You can add a single URL to our crawler ignore list with an easy API method. This is primarily used to quickly add just a single URL, for instance in an overview-screen where you allow users to whitelist URLs.

If you want to ignore several URLs, or remove ignored URLs, please see the more extended update-broken-links-settings API call.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/monitors/1/add-to-broken-links-whitelist \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{
          "whitelistUrl": "https://externalsite.tld/page1"
        }'

Modifying monitor settings

We now support updating all monitor settings from a single endpoint. But we still support updating some settings on the following dedicated endpoints.

You can control whether our crawler should check for external links or stick to internal pages only.

Additionally, you can update the monitor settings and ignore URLs that are known to be broken (or out of your control), so we no longer report if we find broken pages listed on them.

Note: this lists the source URLs, not the destinations.

$ OHDEAR_TOKEN="your API token"
$ curl -X PUT https://ohdear.app/api/monitors/1/update-broken-links-settings \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{
          "broken_links_check_include_external_links": true,
          "broken_links_whitelisted_urls_string":"
https://externalsite.tld/page1
https://externalsite.tld/page2"
        }'

The inputs are as follows:

  • broken_links_check_include_external_links: boolean value, either true or false
  • broken_links_whitelisted_urls_string: string value, new-line separated list of fully qualified URLs that we should ignore and not report on

Both can be used independently, if you just want to update the broken_links_check_include_external_links setting, there is no need to also submit the list of ignored URLs.

Updating the uptime check payload

Oh Dear can send form data along with the uptime check for all non-GET requests.

You can update the sent payload by performing this PUT request:

$ OHDEAR_TOKEN="your API token"
$ curl -X PUT https://ohdear.app/api/monitors/1/update-uptime-check-payload \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '[
          {"name": "my-name", "value": "my-value"}
        ]'

Checks

Get all checks for a particular monitor

To know what checks are available to you, you'll need to query the monitors API call first. Per monitor, you'll be given a set of check IDs.

In this example, we'll get the details for monitor ID 99. Notice the checks data attribute.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors/99 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

Here's the response:

{
  "id": 1,
  "url": "https://yoursite.tld",
  ...
  "checks": [
    {
      "id": 1,
      "type": "uptime",
      "label": "Uptime",
      "enabled": true,
      "latest_run_ended_at": "2019-09-16 07:29:02",
      "latest_run_result": "succeeded",
      "settings": []
    },
    {
      "id": 2,
      "type": "broken_links",
      "label": "Broken links",
      "enabled": true,
      "latest_run_ended_at": "2019-09-16 07:29:05",
      "latest_run_result": "failed",
      "settings": []
    },
    {
      "id": 3,
      "type": "mixed_content",
      "label": "Mixed content",
      "enabled": true,
      "latest_run_ended_at": "2019-09-16 07:29:05",
      "latest_run_result": "succeeded",
      "settings": []
    },
    {
      "id": 4,
      "type": "certificate_health",
      "label": "Certificate health",
      "enabled": true,
      "latest_run_ended_at": "2019-09-16 07:29:02",
      "latest_run_result": "succeeded",
      "settings": []
    }
  ]
}

Per check, you'll see its id. Once you know that ID, you can enable or disable that check or request a new run, on the spot.

Enable & disable a check

Let's take the first check ID, 1, as an example. First, we'll enable the check.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/checks/1/enable \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

You make a POST call to /api/checks/{id}/enable to enable that check. As a result, you'll be given a confirmation payload.

{
    "id": 405,
    "type": "uptime",
    "enabled": true
}

To disable, make a similar POST to the /disable endpoint.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/checks/1/disable \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

The resulting payload will confirm the enabled attribute is set to false.

{
    "id": 405,
    "type": "uptime",
    "enabled": false
}

From that point forward, we'll no longer run that particular check.

If you want to trigger an on-demand run of a check, for instance a new mixed content or broken links scan, you can send an API call to the /request-run endpoint.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/checks/1/request-run \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

To confirm, we'll give the details of the check once again.

Snooze and unsnooze a check

You can temporarily snooze, or silence, a particular check. We will still perform the checks, but won't send out any notifications in the period that a check is snoozed. For more details on how this option works, have a look at our snooze documentation.

If you prefer to stop monitoring a check for a particular period, it's better to disable it and enable it again later on.

To snooze a check, you can call the /api/checks/{id}/snooze endpoint and tell it how long the check should be snoozed using the minutes payload, formatted in JSON.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/checks/1/snooze \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{"minutes":"10"}'

If you receive an HTTP/1.1 200 OK response, the snooze was applied correctly.

To unsnooze, or remove the silencing of alerts, you can call the /api/checks/{id}/unsnooze endpoint.

$ curl -X POST https://ohdear.app/api/checks/1/unsnooze \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

If you once again received an HTTP/1.1 200 OK response, the snooze was correctly removed.

Snooze and unsnooze a scheduled task

In addition to snoozing the entire check, you can also snooze individual scheduled tasks.

To snooze a specific scheduled task, you can call the /api/cron-checks/{task_id}/snooze endpoint and tell it how long the check should be snoozed using the minutes payload, formatted in JSON.

Call /api/cron-checks/{task_id}/unsnooze to remove any active snooze for this task. Tip: You can find the cron_id by calling the /api/monitors/{monitor_id}/cron-checks endpoint or by looking at the cron check details in the Oh Dear application.

Snooze and unsnooze an application health check

You can also snooze and unsnooze an individual application health check.

To snooze an application health check, you can call the /api/monitors/{monitor_id}/application-health-checks/{health_check_id}/snooze endpoint and tell it how long the check should be snoozed using the minutes payload, formatted in JSON.

Call /api/monitors/{monitor_id}/application-health-checks/{health_check_id}/unsnooze to remove any active snooze for this check.

Tip: You can find the application health check id by calling the /api/monitors/{monitor_id}/application-health-checks endpoint or by looking at the application health check details in the Oh Dear application.

Return properties

The response gives you some basic details of a check.

  • id: an internal identifier to Oh Dear
  • type: what type of check this is. Possible values are: uptime, performance, broken_links, certificate_health, mixed_content, cron, dns and application_health.
  • label: a human readable label of the check
  • enabled: a boolean value to indicate if this check is enabled or not. Possible values are: true or false.
  • latest_run_ended_at: a timestamp, in UTC, of the last time this check was run
  • latest_run_result: the status of the last run, can be pending, succeeded, warning or failed
  • summary: a human readable summary of the check result
  • active_snooze: an object containing the details of the active snooze for this check, or null if no snooze is active

More properties will be added over time.

Active snooze

The active_snooze property is an object containing the details of the active snooze for this check, or null if no snooze is active.

"active_snooze": {
  "id": 1,
  "starts_at": "2025-04-30 11:35:20",
  "ends_at": "2025-04-30 11:40:20",
  "snoozed_by": "Sean White",
  "unsnoozed_by": null,
  "source": "API",
  "created_at": "2025-04-30 11:35:20"
}

Uptime

If a monitor has the uptime check enabled, you can query the uptime percentages via the /api/uptime endpoint. To use this endpoint you'll need to now the monitor id of your monitor. You can get all monitors ids by calling the get all monitors endpoint.

To retrieve you'll need to specify a period via the started_at and ended_at filters. The period may not be longer than 2 years. The split value determines how fine grained the answer periods should be. Valid values are hour, day and month.

In this example we are going to get the uptime for monitor id 1.

$ OHDEAR_TOKEN="your API token"
$ curl "https://ohdear.app/api/monitors/1/uptime?filter[started_at]=20180101000000&filter[ended_at]=20180101235959&split=day" \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

Here's what the output could look like:

[
   {"datetime":"2018-01-01 00:00:00","uptime_percentage":100},
   {"datetime":"2018-01-01 01:00:00","uptime_percentage":99.76},
   {"datetime":"2018-01-01 02:00:00","uptime_percentage":100},
   {"datetime":"2018-01-01 03:00:00","uptime_percentage":98.34},
   ...
   {"datetime":"2018-01-01 23:00:00","uptime_percentage":100}
]

Downtime

You can query the downtime via the /api/monitors/{monitorId}/downtime endpoint. To use this endpoint you'll need to now the monitor id of your monitor. You can get all monitors ids by calling the get all monitors endpoint.

To retrieve you'll need to specify a period via the started_at and ended_at filters.

In this example we are going to get the downtime periods for monitor number 1.

$ OHDEAR_TOKEN="your API token"
$ curl "https://ohdear.app/api/monitors/1/downtime?filter[started_at]=20180101150000&filter[ended_at]=20180101160000" \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

Here's what the output could look like:

{
    "data": [
        {
            "id": 1,
            "started_at": "2018-01-01 15:00:00",
            "ended_at": "2018-01-01 15:07:00",
            "notes_html": "We contacted our provider and they fixed the issue.",
            "notes_markdown": "We contacted our provider and they fixed the issue."
        }
    ]
}

Deleting a downtime period

You can delete a downtime period by calling the /api/downtime/{downtimeId} endpoint. Make sure to use the DELETE http verb. To use this endpoint you'll need to now the downtime id, which you can get to by calling the get downtime endpoint.

Uptime Metrics

Retrieving uptime metrics

You can retrieve uptime metrics using different endpoints depending on your monitor type. Each monitor type has its own dedicated endpoint that returns metrics specific to that monitoring method. You'll need to know your monitor's ID first.

A filter is necessary: you decide from when to when the data should be returned.

In this example we'll get a list of all monitors and use the monitor ID to retrieve the uptime metrics.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

{
  "data": [
    {
      "id": 1,
      "url": "https://www.yoursite.tld",
      "type": "http",
      "...",
    }
  ]
}

HTTP Website Uptime Metrics

For HTTP website monitors (type: http), use the /api/http-uptime-metrics endpoint to get detailed performance and timing data.

$ curl https://ohdear.app/api/monitors/1/http-uptime-metrics?filter[start]=20240419132501&filter[end]=20240420132501&filter[group_by]=minute \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

{
  "data": [
    {
      "dns_time_in_seconds": 0.001399,
      "tcp_time_in_seconds": 0.01651,
      "ssl_handshake_time_in_seconds": 0.053852,
      "remote_server_processing_time_in_seconds": 0.021595,
      "download_time_in_seconds": 0.000535,
      "total_time_in_seconds": 0.09389099999999999,
      "curl": {
        "namelookup_time": 0.001399,
        "connect_time": 0.017909,
        "appconnect_time": 0.071761,
        "starttransfer_time": 0.093356,
        "pretransfer_time": 0.071864,
        "redirect_time": 0,
        "total_time": 0.093891,
        "header_size": 1261,
        "size_download": 12814,
        "speed_download": 137784
      },
      "date": "2024-04-19 13:25:00"
    },
    {
      ...
    }
  ]
}

Ping Uptime Metrics

For ping monitors (type: ping), use the /api/ping-uptime-metrics endpoint to get ICMP ping response data.

$ curl https://ohdear.app/api/monitors/1/ping-uptime-metrics?filter[start]=20240419132501&filter[end]=20240420132501&filter[group_by]=minute \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

{
  "data": [
    {
      "minimum_time_in_ms": 12.5,
      "maximum_time_in_ms": 45.2,
      "average_time_in_ms": 28.7,
      "packet_loss_percentage": 0.0,
      "uptime_percentage": 100.0,
      "downtime_percentage": 0.0,
      "uptime_seconds": 60,
      "downtime_seconds": 0,
      "date": "2024-04-19 13:25:00"
    },
    {
      ...
    }
  ]
}

TCP Port Uptime Metrics

For TCP port monitors (type: tcp), use the /api/tcp-uptime-metrics endpoint to get TCP connection data.

$ curl https://ohdear.app/api/monitors/1/tcp-uptime-metrics?filter[start]=20240419132501&filter[end]=20240420132501&filter[group_by]=minute \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

{
  "data": [
    {
      "time_to_connect_in_ms": 15.3,
      "uptime_percentage": 100.0,
      "downtime_percentage": 0.0,
      "uptime_seconds": 60,
      "downtime_seconds": 0,
      "date": "2024-04-19 13:25:00"
    },
    {
      ...
    }
  ]
}

If there are no metrics in the requested period, you'll receive an empty data array.

{
    "data": []
}

Selecting the granularity for data

In the examples above, we grouped the metrics per minute. These records are stored for roughly 14 days. We also store metrics per hour (which are kept for a couple of weeks) and per day (which are kept eternally).

You can request the hourly and daily records by passing hour or day to group_by.

HTTP uptime metrics data explained

The API returns detailed performance metrics for HTTP monitors:

{
    "dns_time_in_seconds": 0.001399,
    "tcp_time_in_seconds": 0.01651,
    "ssl_handshake_time_in_seconds": 0.053852,
    "remote_server_processing_time_in_seconds": 0.021595,
    "download_time_in_seconds": 0.000535,
    "total_time_in_seconds": 0.09389099999999999,
    "curl": {
        "namelookup_time": 0.001399,
        "connect_time": 0.017909,
        "appconnect_time": 0.071761,
        "starttransfer_time": 0.093356,
        "pretransfer_time": 0.071864,
        "redirect_time": 0,
        "total_time": 0.093891,
        "header_size": 1261,
        "size_download": 12814,
        "speed_download": 137784
    },
    "date": "2024-04-19 13:25:00"
}

In the curl key, you find the raw performance values that curl produces when we called your monitor. The timing values are expressed in seconds. So 0.0429 can be read as 42.9ms. By multiplying each value by 1,000, you get the value in milliseconds.

The data represents the following part of the request:

  • dns_time_in_seconds: The time it takes to resolve the domain name to an IP address via DNS.
  • tcp_time_in_seconds: The time it takes to connect to the remote host (TCP three-way handshake).
  • ssl_handshake_time_in_seconds: The total time it took for the TLS handshake to complete (cipher negotiation & encryption).
  • remote_server_processing_time_in_seconds: The time it took the server to process the request and start sending the first byte of the page.
  • download_time_in_seconds: The time, in seconds, it took for the page to be downloaded.
  • total_time_in_seconds: The complete request duration from start to finish.

Ping uptime metrics data explained

The API returns ping-specific metrics for ping monitors:

  • minimum_time_in_ms: The fastest ping response time in the period (milliseconds).
  • maximum_time_in_ms: The slowest ping response time in the period (milliseconds).
  • average_time_in_ms: The average ping response time in the period (milliseconds).
  • packet_loss_percentage: Percentage of ping packets that were lost.
  • uptime_percentage: Percentage of time the host was reachable via ping.
  • downtime_percentage: Percentage of time the host was unreachable.
  • uptime_seconds: Total seconds the host was reachable in this period.
  • downtime_seconds: Total seconds the host was unreachable in this period.

TCP uptime metrics data explained

The API returns TCP connection metrics for TCP monitors:

  • time_to_connect_in_ms: The time it took to establish a TCP connection (milliseconds).
  • uptime_percentage: Percentage of time the port was accessible.
  • downtime_percentage: Percentage of time the port was inaccessible.
  • uptime_seconds: Total seconds the port was accessible in this period.
  • downtime_seconds: Total seconds the port was inaccessible in this period.

If a monitor has the broken links check enabled, you can get all detected broken links via the /api/broken-links endpoint. To use this endpoint you'll need to now the monitor id of your monitor. You can get all monitors ids by calling the get all monitors endpoint.

In this example we are going to get the broken-links for monitor number 1.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/broken-links/1 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

Here's what the output could look like:

{
    "data": [
        {
            "crawled_url": "https://example.com/broken",
            "relative_crawled_url": "/broken",
            "status_code": 404,
            "found_on_url": "https://example.com",
            "relative_found_on_url": "https://example.com",
            "internal": true
        }
    ]
}

Mixed content

Retrieving mixed content

If a monitor has the mixed content check enabled, you can get all detected mixed content via the /api/mixed-content endpoint. To use this endpoint you'll need to now the monitor id of your monitor. You can get all monitors ids by calling the get all monitors endpoint.

In this example we are going to get the broken-links for monitor number 1.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/mixed-content/1 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

Here's what the output could look like:

{
    "data": [
        {
            "element_name": "img",
            "mixed_content_url": "http://example.com/image.jpg",
            "found_on_url": "https://example.com"
        }
    ]
}

Certificate Health

Retrieving info on the certificate health

If a monitor has the certificate health check enabled, you can get feedback on the result via the /api/certificate-health endpoint. To use this endpoint you'll need to now the monitor id of your monitor. You can get all monitors ids by calling the get all monitors endpoint.

Here's an example where we get the certificate health of the monitor with ID 1.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/certificate-health/1 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

The output will be an object with these 3 keys:

  • certificate_details: an array with some details on the detected certificate
  • certificate_checks: an array with info on the checks we performed on the certificate
  • certificate_chain_issuers: an array containing the names of all the issuers in the complete certificate chain

Here's what it looks like.

{
    "certificate_details": {
        "issuer": "Let's Encrypt Authority X3",
        "valid_from": "2019-09-10 15:16:01",
        "valid_until": "2019-12-09 15:16:01"
    },
    "certificate_checks": [
        {
            "type": "notFound",
            "label": "Certificate present",
            "passed": true
        },
        {
            "type": "expiresSoon",
            "label": "Will not expire in the next 14 days",
            "passed": true
        },
        {
            "type": "coversWrongDomain",
            "label": "Covers the right domain",
            "passed": true
        },
        {
            "type": "doesNotConnectWithRootCertificate",
            "label": "Connects with a root certificate",
            "passed": true
        },
        {
            "type": "notYetActive",
            "label": "Is currently active",
            "passed": true
        },
        {
            "type": "isSelfSigned",
            "label": "Is not self signed",
            "passed": true
        },
        {
            "type": "usesInvalidHash",
            "label": "Uses valid hash",
            "passed": true
        },
        {
            "type": "hasExpired",
            "label": "Has not expired",
            "passed": true
        },
        {
            "type": "hasChanged",
            "label": "Unchanged since last checked",
            "passed": true
        }
    ],
    "certificate_chain_issuers": [
        "US, Let's Encrypt, Let's Encrypt Authority X3",
        "Digital Signature Trust Co., DST Root CA X3"
    ]
}

To determine the overal health of the certificate, you can look at all the checks in the certificate_checks result.

Those with a value of "passed": false have a failed check.

Retrieving detected certificates

This endpoint allows you to view all certificates that have been detected for a specific monitor, including historical certificates.

Here's an example where we get all detected certificates for the monitor with ID 1.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors/1/detected-certificates \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

The response will contain a paginated list of detected certificates:

{
    "data": [
        {
            "id": 123,
            "monitor_id": 1,
            "fingerprint": "abcd1234567890abcd1234567890abcd12345678",
            "certificate_details": {
                "issuer": "Let's Encrypt Authority X3",
                "domain": "example.com",
                "additional_domains": [
                    "www.example.com"
                ],
                "valid_from": "2023-10-01T12:00:00Z",
                "valid_until": "2024-01-01T12:00:00Z",
                "days_until_expiration": 45,
                "signature_algorithm": "sha256WithRSAEncryption",
                "is_valid": true,
                "is_expired": false
            },
            "created_at": "2023-10-01T12:30:00Z",
            "updated_at": "2023-10-01T12:30:00Z"
        }
    ],
    "links": {
        "first": "https://ohdear.app/api/monitors/1/detected-certificates?page=1",
        "last": "https://ohdear.app/api/monitors/1/detected-certificates?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "per_page": 15,
        "to": 1,
        "total": 1
    }
}

You can also retrieve a specific detected certificate by its ID:

$ curl https://ohdear.app/api/monitors/1/detected-certificates/123 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

Filtering and sorting

You can filter detected certificates by fingerprint:

$ curl https://ohdear.app/api/monitors/1/detected-certificates?filter[fingerprint]=abcd1234567890abcd1234567890abcd12345678 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

You can sort the results by created_at, updated_at, or fingerprint:

$ curl https://ohdear.app/api/monitors/1/detected-certificates?sort=-created_at \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

Use a minus sign (-) before the field name to sort in descending order.

User Info

Retrieve your team & user info

Make a GET request to the /api/me endpoint.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/me \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

If the API call succeeded, you'll be presented with your team & user info.

Return properties

Here's an example payload of the API call.

{
    "id": 1,
    "name": "Firstname Lastname",
    "email": "[email protected]",
    "photo_url": "https://www.gravatar.com/avatar/...jpg",
    "teams": [
        {
            "id": 1,
            "name": "Your Team Name"
        }
    ]
}

Let's look at all the properties that are returned.

  • id: the identifier of the user this API key belongs to.
  • name, email and photo_url: personal information about your account.
  • teams: an array of teams this user belongs to. A user can be a member of several teams.

The teams property has an id (team ID) and a human readable name. The team id will be used throughout all API calls to determine what team you want to add monitors to.

Status Pages

You can make a GET HTTP call to the /api/status-pages endpoint to retrieve all your configured status pages.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/status-pages \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

As a result, you'll get a list of all your status pages and their metadata.

{
  "data": [
    {
      "id": 1,
      "team": {
        "id": 1,
        "name": "Your Team Name"
      },
      "title": "Your Status Page Title",
      "domain": "status.your-domain.tld",
      "slug": "autem-quos-accusantium-esse-ex-numquam-odio",
      "full_url": "https://ohdear.app/status-page/autem-quos-accusantium-esse-ex-numquam-odio",
      "timezone": "Europe/Brussels",
      "summarized_status": "up",
      "monitors": [
        {
          "id": 1,
          "url": "https://site1.tld",
          ...
        },
        {
          "id": 7,
          "url": "https://site2.tld",
          ...
        }
      ],
      "created_at": "2019-09-13 07:06:51",
      "updated_at": "2019-09-13 07:06:51"
    }
  ],
  "links": {
    "first": "https://ohdear.app/api/status-pages?page%5Bnumber%5D=1",
    "last": "https://ohdear.app/api/status-pages?page%5Bnumber%5D=1",
    "prev": null,
    "next": null
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 1,
    "path": "https://ohdear.app/api/status-pages",
    "per_page": 200,
    "to": 2,
    "total": 2
  }
}

There's a lot of data in there, let's unpack it.

The following fields are available in the output:

  • data: this is an array that contains a list of all your status pages.
  • team: because you can belong to multiple teams, we'll clarify which team this status page belongs to.
  • summarized_status: this can either be up, down or unknown. It's down when at least one of the monitors in the status page has an uptime check that reported it as down.
  • monitors: this is an array with all monitors that are added to this status page. It will also contain all checks from each website, so you have all information easily available to you.
  • prevent_indexing: this is a boolean value that determines whether the status page should be indexed by search engines
  • the links and meta output are for pagination when you have many status pages.

Retrieve a single status page

You can also pass the ID of a status page to the /api/status-pages/{id} endpoint to get the details of a specific status page.

In this example, we'll retrieve the status page with ID 1.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/status-pages/1 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

The resulting output will contain the details of that specific status page. Note that it does not have the data container that is used when retrieving all monitors through the API.

{
  "id": 1,
  "team": {
    "id": 1,
    "name": "Your Team Name"
  },
  "title": "Your Status Page Title",
  ...
  "updates": [
    {
      "id": 65,
      "title": "Your status page update title",
      ...
    },
    ...
  ],
  "monitors": [
    {
      "id": 1,
      "url": "https://site1.tld",
    },
    ...
  ]
}

In addition to the similar output of the /api/status-pages endpoint described above, when you get the data from a single status page we'll also include its updates.

This will contain an array of all status page updates & messages that have been posted to your status page.

For more details, have a look at our status page updates API documentation.

The rest of the data is similar to the output described above.

Create a status page

You can create a status page by making a POST HTTP call to the /api/status-pages endpoint. The following fields are required:

  • team_id: the ID of the team you want to create the status page for
  • title: the title of your status page
  • monitors: an array of monitors you want to add to the status page
    • id: the ID of the monitor you want to add to the status page
    • clickable: boolean whether the monitor should be clickable on the status page
{
  "team_id": 1,
  "title": "My New Status Page",
  "monitors": [
    { "id": 1, "clickable": true },
    { "id": 2, "clickable": false }
  ]
}

Delete a status page

If you make a DELETE HTTP call to the /api/status-pages/ID, you can delete a status page through the API.

In this example we are going to delete the status page with ID 99.

$ OHDEAR_TOKEN="your API token"
$ curl -i -X DELETE https://ohdear.app/api/status-pages/99 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

Add monitors to a status page

You can add a monitor to an existing status page by making a POST HTTP call to the /api/status-pages/{statusPage}/monitors endpoint. The following fields are accepted:

  • sync: (optional) boolean whether existing monitors should be removed and replaced with the given monitors
  • monitors: an array of monitors you want to add to the status page
    • id: the ID of the monitor you want to add to the status page
    • clickable: boolean whether the monitor should be clickable on the status page
{
	"sync": true,
	"monitors": [
		{ "id": 17, "clickable": true }
	]
}

The sync field is optional. If it's set to true, the existing monitors will be removed and replaced with the given monitors. If it's set to false, the given monitors will be appended to the status page.

Remove a monitor from a status page

You can also remove a monitor from a status page by making a DELETE HTTP call to the /api/status-pages/{statusPage}/monitors/{monitor} endpoint.

$ OHDEAR_TOKEN="your API token"
$ curl -i -X DELETE https://ohdear.app/api/status-pages/1/monitors/17 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

A HTTP/1.1 204 No Content response indicates that the monitor was successfully removed from the status page.

HTTP/1.1 204 No Content
X-RateLimit-Limit: 180
X-RateLimit-Remaining: 176
...

Each status page can contain updates or messages. These are used to communicate to your customers or clients in times of downtime or maintenance.

You can post these messages from your dashboard or with our API.

To list the updates or messages from a single status page, you'll need to know its ID. You can retrieve that using the /api/status-pages API call explained in the status pages API documentation.

Once you have your ID, you can get the details as follows:

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/status-pages/1/updates \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

The resulting output will contain an array of all the updates to that specific status page.

If there are no status messages, the array will be empty.

{
    "data": []
}

If there are messages, we'll return those in the data field.

{
    "data": [
        {
            "id": 65,
            "title": "3rd party API issue resolved",
            "text": "We have contacted our API provider and they have resolved the situation, everything is back to normal. We apologize for the inconvenience!",
            "pinned": false,
            "severity": "resolved",
            "time": "2019-09-16 08:24:00",
            "status_page_url": "https://ohdear.app/status-page/your-slug"
        },
        {
            "id": 64,
            "title": "We are experiencing an outage on our service",
            "text": "Due to errors in our 3rd party API provider, we are experiencing an outage of our website. Our team is working on getting it resolved ASAP.",
            "pinned": false,
            "severity": "high",
            "time": "2019-09-16 08:11:00",
            "status_page_url": "https://ohdear.app/status-page/your-slug"
        }
    ]
}

These are sorted by newest message to oldest. The output contains the following fields:

  • id: the ID of that particular update
  • title and text: these are shown on your status page
  • pinned: when set to true, this update will be posted at the very top of your status page. There can only be 1 pinned message.
  • severity: these can be info, warning, high, resolved or scheduled. You are free to use these any way you like. The resolved updates will be posted in green on your status page to inform the user the problem has been resolved.
  • time: the date in UTC at which this status page was posted. This will get translated to your configured timezone.

Post a new update to your status page

To add a new update or message to your status page, you can make a POST call to the /api/status-page-updates endpoint.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/status-page-updates \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{"status_page_id":1, "title":"Our site is down", "text":"We are working on it!", "severity":"high", "time":"2019-09-16 10:24","pinned":true}'

As a confirmation, we'll return an HTTP/1.1 201 Created with the received data fields.

{
    "id": 66,
    "title": "Our site is down",
    "text": "We are working on it!",
    "pinned": true,
    "severity": "high",
    "time": "2019-09-16 08:24:00",
    "status_page_url": "https://ohdear.app/status-page/your-slug"
}

The POST API call should contain a valid json body with the following fields.

  • status_page_id: the ID of the status page where you want to add this update.
  • title: the titel of your status update
  • text: a longer text describing what is happening or how you're resolving the problem
  • severity are: info, warning, high, resolved or scheduled.
  • time: a date field that acts as a timestamp for this update, should be in UTC in the Y-m-d H:i format.
  • pinned: whether this status page message should be pinned to the very top of the page. There can only be 1 pinned message, when set to true it will remove the pinned attribute from any other message.
  • status_page_url: the public URL of your status page. If you have configured a custom domain, this will point to your custom domain - otherwise it'll be the URL with your status page slug on the ohdear.app domain.

Delete an update on your status page

Let's use the API to delete the status message we just posted above, the one with ID 66.

$ OHDEAR_TOKEN="your API token"
$ curl -X DELETE https://ohdear.app/api/status-page-updates/66 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

If the response code is HTTP/1.1 204 No Content, the deletion of the status update was successful.

Maintenance windows

Retrieving maintenance periods

You can list all maintenance windows by calling the /maintenance-periods endpoint. This can be done per website in your Oh Dear account, so you'll need to know your monitors's ID first.

In this example we'll get a list of all monitors and use the monitor ID to retrieve the maintenance periods.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

{
  "data": [
    {
      "id": 1,
      "url": "https://www.yoursite.tld",
      "...",
    }
  ]
}


$ curl https://ohdear.app/api/monitors/1/maintenance-periods \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

{
  "data": [
    {
      "id": 101,
      "monitor_id": 1,
      "name": "Deployment"
      "starts_at": "2020-01-09 10:37:24",
      "ends_at": "2020-01-09 10:42:24"
    },
    {
      "id": 102,
      "monitor_id": 1,
      "name": ""
      "starts_at": "2020-01-09 10:37:32",
      "ends_at": "2020-01-09 10:42:32"
    },
}

If there are no maintenance periods, you'll receive an empty data array.

{
    "data": []
}

If there are maintenance windows, we'll show the time of each start end end period.

{
    "data": [
        {
            "starts_at": "2020-01-09 14:00:00",
            "ends_at": "2020-01-10 14:00:00"
        }
    ]
}

These dates are all shown in the timezone of your team. If your timezone is configured as America/Vancouver, you'll see the dates & times shown in your own local timezone.

Creating a new maintenance period on-demand

This method applies on a per-monitor basis. If you want to put the monitor with ID 1 in maintenance right now, you can call this API method.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/monitors/1/start-maintenance \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

{
  "id": 101,
  "monitor_id": 1,
  "name": "Optional name of your maintenance period"
  "starts_at": "2020-01-09 10:28:54",
  "ends_at": "2020-01-09 11:28:54"
}

This will start a maintenance period that, by default, will last for 60 minutes. In the response, you'll find the confirmation of the start- and end-date.

To stop the maintenance period, call the /stop-maintenance endpoint.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/monitors/1/stop-maintenance \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

This will change the end-date of the currently active maintenance period to the time you've called the API.

This way, you still have a list of maintenance periods to look back on (as an audit list).

On-demand maintenance window with limited duration

If you prefer, you can also pass along the amount of seconds a maintenance period needs to be active in the /start-maintenance API call. This can be lowered or increased depending on your needs and provides a safe fallback in case your scripts execute the start-maintenance API call, but never get to the stop-maintenance call.

To do so, add a stop_maintenance_after_seconds POST value in JSON, as shown here.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/monitors/1/start-maintenance \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{"stop_maintenance_after_seconds":"300"}'

{
  "id": 101,
  "monitor_id": 1,
  "starts_at": "2020-01-09 10:37:32",
  "ends_at": "2020-01-09 10:42:32"
}

This will create a maintenance period for 300 seconds, or 5 minutes, as confirmed by the JSON output.

Creating a maintenance period with custom start and end times

If you don't use the on-demand maintenance windows as shown above, you can create maintenance windows with a custom start- and end-date. This allows you to schedule maintenance ahead of times.

Since maintenance windows are applicable per website, you need the monitor ID of the website you want to put in maintenance.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/maintenance-periods \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{"monitor_id":"1", "starts_at":"2020-02-01 14:00","ends_at":"2020-02-01 18:00"}'

{
  "id": 101,
  "monitor_id": "1",
  "starts_at": "2020-02-01 14:00:00",
  "ends_at": "2020-02-01 18:00:00"
}

The API expects the dates to be formatted as Y:m:d H:i (no seconds) and the ends_at needs to be further in the future than the starts_at.

All dates should be in your local timezone, as configured in your team settings. (You don't need to convert anything to UTC, write the dates as they feel natural to you.)

Deleting a maintenance period

To delete a maintenance period, you need its ID. That can be retrieved with the /maintenance-periods API call, as shown above.

$ OHDEAR_TOKEN="your API token"
$ curl -X DELETE https://ohdear.app/api/maintenance-periods/101 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

The example above deletes the maintenance period with ID 101.

Cron Job Monitoring

Get all cron jobs for a website

Cron job monitors are attached to a website in Oh Dear. To retrieve a list of monitored cron jobs, you'll the monitor ID first.

The /api/monitors/{$monitorId}/cron-checks endpoint lists all your cron job monitors for that particular monitor.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors/1/cron-checks \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

As a result, you get the details for every cron job monitor attached to this monitor, together with its latest status:

{
    "data": [
        {
            "id": 1,
            "uuid": "0d210b09",
            "name": "cronjob number one",
            "type": "simple",
            "description": "a description goes here",
            "frequency_in_minutes": 15,
            "grace_time_in_minutes": 5,
            "cron_expression": null,
            "server_timezone": "Europe/Brussels",
            "ping_url": "https://ping.ohdear.app/0d210b09",
            "created_at": "2020-07-28 13:27:02",
            "latest_result": "pingFinished",
            "latest_ping_at": "2020-11-16 09:19:40",
            "human_readable_latest_ping_at": "5 minutes ago"
        },
        {
            "id": 2,
            "uuid": "8eb64808",
            "name": "cronjob number two",
            "type": "simple",
            "description": "a description goes here",
            "frequency_in_minutes": 60,
            "grace_time_in_minutes": 10,
            "cron_expression": null,
            "server_timezone": "Europe/Brussels",
            "ping_url": "https://ping.ohdear.app/8eb64808",
            "created_at": "2020-07-28 13:27:02",
            "latest_result": "pingFinished",
            "latest_ping_at": "2020-11-16 09:19:40",
            "human_readable_latest_ping_at": "5 minutes ago"
        }
    ]
}

The response is a JSON encoded array with all the details of the cron jobs you defined to be monitored.

Result types

The latest_result response in the API can be one of the following values.

These are values actively submitted by your application to your cron monitoring URL endpoint:

  • pingStarting: we have received the "started" pingback from your application
  • pingFinished: we have received the "finished" pingback from your application, indicating no errors occurred
  • pingFailed: we have received the "failed" pingback from your application, indicating errors have occured

These are values we may have detected ourselves, without active participation from your application:

  • checkRanTooLate: because we did not receive a new ping URL on time, we have marked the cron task as failed because it ran too late
  • sentCronNotExutedOnTimeNotification: because the check didn't execute on time, we have sent the "did not run on time" notification
  • sentPingFailedNotification: your application reported us that the last ping contained a failure, we have now sent a notification that the ping failed

The success states of a cronjob are determined only by the pingFinished result. All other types indicate that we are either waiting on a ping, have received a failed ping, or have received no ping at all.

Create a simple cron job monitor

You can create a new cron job monitor using our API. You'll need the monitor ID where you want to attach the new monitor to.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/monitors/1/cron-checks \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{
      "name": "your cron job name",
      "type": "simple",
      "frequency_in_minutes": 10,
      "grace_time_in_minutes": 15,
      "description": "a description goes here"
    }'

The example above will create a new cron job monitor by providing both the frequency and the gracetime. Note how the type is set to simple.

The response will include the Ping URL you can point your cron jobs to:

{
    "id": 12,
    "uuid": "ba4322cf",
    "name": "your cron job name",
    "type": "simple",
    "description": "a description goes here",
    "frequency_in_minutes": 10,
    "grace_time_in_minutes": 15,
    "cron_expression": "",
    "server_timezone": null,
    "ping_url": "https://ping.ohdear.app/ba4322cf",
    "created_at": "2020-08-03 12:18:33"
}

The ping_url should be used in your scripts to report back the status of your cron jobs to us.

Create a new cron job monitor via crontab expression

Alternatively, you can create a new cron job monitor using the crontab expression as well.

Note how in this example the type is set to cron instead of simple.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/monitors/1/cron-checks \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{
      "name": "your cron job name",
      "type": "cron",
      "cron_expression":"* * * * *",
      "grace_time_in_minutes": 10,
      "description": "a description goes here",
      "server_timezone": "UTC"
    }'

The example above will create a new cron job monitor using the crontab expression * * * * * to indicate it should run every minute.

The response will include the Ping URL you can point your cron jobs to:

{
    "id": 10,
    "uuid": "19eea41a",
    "name": "your cron job name",
    "type": "cron",
    "description": "a description goes here",
    "frequency_in_minutes": 0,
    "grace_time_in_minutes": 10,
    "cron_expression": "* * * * *",
    "server_timezone": "UTC",
    "ping_url": "https://ping.ohdear.app/19eea41a",
    "created_at": "2020-08-03 12:13:53"
}

The ping_url should be used in your scripts to report back the status of your cron jobs to us.

Bulk cron job synchronization

For our PHP-SDK, we've created a mass-update endpoint you can use to update all cron jobs for a single monitor. This will remove any cron job not explicitly in this sync API call, so it is a destructive action.

$ OHDEAR_TOKEN="your API token"
$ curl -X POST https://ohdear.app/api/monitors/1/cron-checks/sync \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{
        "cron_checks": [
          {
            "name": "cron job #1",
            "type": "cron",
            "cron_expression":"* * * * *",
            "grace_time_in_minutes": 10,
            "description": "description for cron #1",
            "server_timezone": "UTC"
          },
          {
            "name": "cron job #2",
            "type": "cron",
            "cron_expression":"* * * * *",
            "grace_time_in_minutes": 10,
            "description": "description for cron #2",
            "server_timezone": "UTC"
          }
        ]
    }'

The return data, if the API call was successful, is a confirmation of each cron job definition together with all its metadata.

[
    {
        "id": 9,
        "uuid": "c1803dce",
        "name": "cron job #1",
        "type": "cron",
        "description": "description for cron #1",
        "frequency_in_minutes": null,
        "grace_time_in_minutes": 10,
        "cron_expression": "* * * * *",
        "server_timezone": "UTC",
        "ping_url": "https://ping.ohdear.app/c1803dce",
        "created_at": "2020-09-03 19:20:25",
        "latest_result": "pingFinished",
        "latest_ping_at": "2020-11-16 09:19:40"
    },
    {
        "id": 10,
        "uuid": "40e8e39e",
        "name": "cron job #2",
        "type": "cron",
        "description": "description for cron #2",
        "frequency_in_minutes": null,
        "grace_time_in_minutes": 10,
        "cron_expression": "* * * * *",
        "server_timezone": "UTC",
        "ping_url": "https://ping.ohdear.app/40e8e39e",
        "created_at": "2020-09-03 19:20:25",
        "latest_result": "pingFinished",
        "latest_ping_at": "2020-11-16 09:19:40"
    }
]

Error handling

If an error occured, you'll be given a non-HTTP/200 response code. The resulting payload might look like this.

{
    "message": "The given data was invalid.",
    "errors": {
        "name": ["There already exist a cron check with this name for this site."]
    }
}

DNS Records

Retrieving DNS history for a monitor

To retrieve a list of your entire Dns history and issues, you'll the monitor ID first.

The /api/monitors/{$monitorId}/dns-history-items endpoint lists all DNS related information we retrieved for your domain.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors/1/dns-history-items \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

As a result, you get the history of the DNS records for your domain, together with all issues we found. Here's an example result for records retrieved for a https://freek.dev.

The results are sorted in reverse chronological order: the topmost item will contain your current DNS records.

{
  "data": [
    {
      "id": 2,
      "authoritative_nameservers": [
        "ns1.openprovider.nl",
        "ns2.openprovider.be",
        "ns3.openprovider.eu"
      ],
      "dns_records": [
        {
          "host": "freek.dev",
          "ttl": 1017,
          "class": "IN",
          "type": "A",
          "ip": "138.197.187.74"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "MX",
          "pri": 10,
          "target": "aspmx.l.google.com"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "MX",
          "pri": 20,
          "target": "alt1.aspmx.l.google.com"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "MX",
          "pri": 20,
          "target": "alt2.aspmx.l.google.com"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "MX",
          "pri": 30,
          "target": "aspmx2.googlemail.com"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "MX",
          "pri": 30,
          "target": "aspmx3.googlemail.com"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "NS",
          "target": "ns1.openprovider.nl"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "NS",
          "target": "ns2.openprovider.be"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "NS",
          "target": "ns3.openprovider.eu"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "SOA",
          "mname": "ns1.openprovider.nl",
          "rname": "dns.openprovider.eu",
          "serial": 2021080201,
          "refresh": 10800,
          "retry": 3600,
          "expire": 604800,
          "minimum_ttl": 3600
        },
        {
          "host": "freek.dev",
          "ttl": 1060,
          "class": "IN",
          "type": "TXT",
          "txt": "v=spf1 include:amazonses.com include:eu.mailgun.org include:spf.factuursturen.be include:sendgrid.net include:_spf.google.com a mx ~all"
        }
      ],
      "raw_dns_records": {
        "ns1.openprovider.nl": [
          "freek.dev.\t\t1017\tIN\tA\t138.197.187.74",
          "freek.dev.\t\t1060\tIN\tTXT\t\"v=spf1 include:amazonses.com include:eu.mailgun.org include:spf.factuursturen.be include:sendgrid.net include:_spf.google.com a mx ~all\"",
          "freek.dev.\t\t4435\tIN\tMX\t10\taspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt1.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt2.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx2.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx3.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tNS\tns1.openprovider.nl.",
          "freek.dev.\t\t4435\tIN\tNS\tns2.openprovider.be.",
          "freek.dev.\t\t4435\tIN\tNS\tns3.openprovider.eu.",
          "freek.dev.\t\t4435\tIN\tSOA\tns1.openprovider.nl.\tdns.openprovider.eu.\t2021080201\t10800\t3600\t604800\t3600"
        ],
        "ns2.openprovider.be": [
          "freek.dev.\t\t1060\tIN\tTXT\t\"v=spf1 include:amazonses.com include:eu.mailgun.org include:spf.factuursturen.be include:sendgrid.net include:_spf.google.com a mx ~all\"",
          "freek.dev.\t\t4435\tIN\tMX\t10\taspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt1.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt2.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx2.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx3.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tNS\tns1.openprovider.nl.",
          "freek.dev.\t\t4435\tIN\tNS\tns2.openprovider.be.",
          "freek.dev.\t\t4435\tIN\tNS\tns3.openprovider.eu.",
          "freek.dev.\t\t4435\tIN\tSOA\tns1.openprovider.nl.\tdns.openprovider.eu.\t2021080201\t10800\t3600\t604800\t3600"
        ],
        "ns3.openprovider.eu": [
          "freek.dev.\t\t1017\tIN\tA\t138.197.187.74",
          "freek.dev.\t\t1060\tIN\tTXT\t\"v=spf1 include:amazonses.com include:eu.mailgun.org include:spf.factuursturen.be include:sendgrid.net include:_spf.google.com a mx ~all\"",
          "freek.dev.\t\t4435\tIN\tMX\t10\taspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt1.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt2.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx2.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx3.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tNS\tns1.openprovider.nl.",
          "freek.dev.\t\t4435\tIN\tNS\tns2.openprovider.be.",
          "freek.dev.\t\t4435\tIN\tNS\tns3.openprovider.eu.",
          "freek.dev.\t\t4435\tIN\tSOA\tns1.openprovider.nl.\tdns.openprovider.eu.\t2021080201\t10800\t3600\t604800\t3600"
        ]
      },
      "issues": [
        {
          "name": "NameserversAreNotInSync",
          "nameservers": [
            "ns2.openprovider.be"
          ]
        }
      ],
      "diff_summary": "No changes",
      "created_at": "2021-11-05 16:07:38"
    },
    {
      "id": 1,
      "authoritative_nameservers": [
        "ns1.openprovider.nl",
        "ns2.openprovider.be",
        "ns3.openprovider.eu"
      ],
      "dns_records": [
        {
          "host": "freek.dev",
          "ttl": 1017,
          "class": "IN",
          "type": "A",
          "ip": "138.197.187.74"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "MX",
          "pri": 10,
          "target": "aspmx.l.google.com"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "MX",
          "pri": 20,
          "target": "alt1.aspmx.l.google.com"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "MX",
          "pri": 20,
          "target": "alt2.aspmx.l.google.com"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "MX",
          "pri": 30,
          "target": "aspmx2.googlemail.com"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "MX",
          "pri": 30,
          "target": "aspmx3.googlemail.com"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "NS",
          "target": "ns1.openprovider.nl"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "NS",
          "target": "ns2.openprovider.be"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "NS",
          "target": "ns3.openprovider.eu"
        },
        {
          "host": "freek.dev",
          "ttl": 4435,
          "class": "IN",
          "type": "SOA",
          "mname": "ns1.openprovider.nl",
          "rname": "dns.openprovider.eu",
          "serial": 2021080201,
          "refresh": 10800,
          "retry": 3600,
          "expire": 604800,
          "minimum_ttl": 3600
        },
        {
          "host": "freek.dev",
          "ttl": 1060,
          "class": "IN",
          "type": "TXT",
          "txt": "v=spf1 include:amazonses.com include:eu.mailgun.org include:spf.factuursturen.be include:sendgrid.net include:_spf.google.com a mx ~all"
        }
      ],
      "raw_dns_records": {
        "ns1.openprovider.nl": [
          "freek.dev.\t\t1017\tIN\tA\t138.197.187.74",
          "freek.dev.\t\t1060\tIN\tTXT\t\"v=spf1 include:amazonses.com include:eu.mailgun.org include:spf.factuursturen.be include:sendgrid.net include:_spf.google.com a mx ~all\"",
          "freek.dev.\t\t4435\tIN\tMX\t10\taspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt1.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt2.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx2.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx3.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tNS\tns1.openprovider.nl.",
          "freek.dev.\t\t4435\tIN\tNS\tns2.openprovider.be.",
          "freek.dev.\t\t4435\tIN\tNS\tns3.openprovider.eu.",
          "freek.dev.\t\t4435\tIN\tSOA\tns1.openprovider.nl.\tdns.openprovider.eu.\t2021080201\t10800\t3600\t604800\t3600"
        ],
        "ns2.openprovider.be": [
          "freek.dev.\t\t1017\tIN\tA\t138.197.187.74",
          "freek.dev.\t\t1060\tIN\tTXT\t\"v=spf1 include:amazonses.com include:eu.mailgun.org include:spf.factuursturen.be include:sendgrid.net include:_spf.google.com a mx ~all\"",
          "freek.dev.\t\t4435\tIN\tMX\t10\taspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt1.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt2.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx2.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx3.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tNS\tns1.openprovider.nl.",
          "freek.dev.\t\t4435\tIN\tNS\tns2.openprovider.be.",
          "freek.dev.\t\t4435\tIN\tNS\tns3.openprovider.eu.",
          "freek.dev.\t\t4435\tIN\tSOA\tns1.openprovider.nl.\tdns.openprovider.eu.\t2021080201\t10800\t3600\t604800\t3600"
        ],
        "ns3.openprovider.eu": [
          "freek.dev.\t\t1017\tIN\tA\t138.197.187.74",
          "freek.dev.\t\t1060\tIN\tTXT\t\"v=spf1 include:amazonses.com include:eu.mailgun.org include:spf.factuursturen.be include:sendgrid.net include:_spf.google.com a mx ~all\"",
          "freek.dev.\t\t4435\tIN\tMX\t10\taspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt1.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t20\talt2.aspmx.l.google.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx2.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tMX\t30\taspmx3.googlemail.com.",
          "freek.dev.\t\t4435\tIN\tNS\tns1.openprovider.nl.",
          "freek.dev.\t\t4435\tIN\tNS\tns2.openprovider.be.",
          "freek.dev.\t\t4435\tIN\tNS\tns3.openprovider.eu.",
          "freek.dev.\t\t4435\tIN\tSOA\tns1.openprovider.nl.\tdns.openprovider.eu.\t2021080201\t10800\t3600\t604800\t3600"
        ]
      },
      "issues": [],
      "diff_summary": "No changes",
      "created_at": "2021-11-05 16:02:38"
    }
}

Possible values of issues

The issues property will return an array with issues we detected. Each item in that array will have a property name that can contain on of the following values:

  • CouldNotDiscoverNameservers: we couldn't find the authoritative nameservers for your domain
  • CouldNotFindAnyDnsRecords: some nameservers of your domain did not return any records. These nameservers are listed in the nameservers property of this array
  • NameserversAreNotInSync: a nameserver of your domain returned different records than the other nameservers. The names of the nameservers that are not in sync are listed in the nameservers property of this array

Application Health Monitoring

Retrieving application health check for a monitor

To retrieve a list of application health checks, you'll need the monitor ID first.

The /api/monitors/{$monitorId}/application-health-checks endpoint lists all application health checks that are running for your monitor.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors/1/application-health-checks \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

This is what could get as a result:

{
    "data": [
        {
            "id": 1,
            "name": "DiskUsage",
            "label": "Disk usage",
            "status": "failed",
            "message": "The disk is nearly full (91%)",
            "short_summary": "91%",
            "meta": {
                "usage": 91
            },
            "detected_at": "2021-01-01 00:00:00",
            "updated_at": "2021-01-01 00:00:00"
        }
    ]
}

Available fields

The message property will be filled when a check failed. It will contain the message that we also have sent via a notification.

The short_summary property contains a string that we'll also display on the application health check overview of your site.

The status property can contain one of the following values:

  • ok: the check was ok
  • warning: the check is closed to failing
  • failed: the check did fail
  • crashed: something went wrong running the check itself
  • skipped: the check wasn't performed in this run

The meta property contains an object that contain extra info that was sent along with the check result.

Retrieving the history of a health check

The /api/monitors/{$monitorId}/application-health-checks/{$applicationHealthCheckId} endpoint lists the history of results of an application health check.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors/1/application-health-checks/1 \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

It will return the history of the check results. The newest items appear first in the list.

{
    "data": [
        {
            "id": 2,
            "status": "ok",
            "message": "",
            "short_summary": "60%",
            "meta": {
                "usage": 60
            },
            "detected_at": "2021-01-01 00:01:00",
            "updated_at": "2021-01-01 00:01:00"
        },
        {
            "id": 1,
            "status": "failed",
            "message": "The disk is nearly full (91%)",
            "short_summary": "91%",
            "meta": {
                "usage": 91
            },
            "detected_at": "2021-01-01 00:00:00",
            "updated_at": "2021-01-01 00:00:00"
        }
    ]
}

Domain monitoring

Retrieving domain information for a site

To retrieve domain information of a site, you'll need the monitor ID first.

The /api/monitors/{$monitorId}/domain endpoint displays domain related information we retrieved for your domain.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors/1/domain \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

The response contains the most important dates of the domain, and the domain status. In the rdap_domain_response key, you'll find the raw response we got from RDAP about your domain. The created_at key contains the date on which we queried RDAP.

Here's example output when monitoring google.com:

{
    "expires_at": "2028-09-14 04:00:00",
    "registered_at": "1997-09-15 04:00:00",
    "last_changed_at": "2019-09-09 15:39:04",
    "last_updated_in_rdap_db_at": "2022-06-11 00:32:59",
    "domain_statuses": {
        "client delete prohibited": true,
        "client transfer prohibited": true,
        "client update prohibited": true,
        "server delete prohibited": true,
        "server transfer prohibited": true,
        "server update prohibited": true
    },
    "rdap_domain_response": {
        "links": [
            {
                "rel": "self",
                "href": "https://rdap.verisign.com/com/v1/domain/GOOGLE.COM",
                "type": "application/rdap+json",
                "value": "https://rdap.verisign.com/com/v1/domain/GOOGLE.COM"
            },
            {
                "rel": "related",
                "href": "https://rdap.markmonitor.com/rdap/domain/GOOGLE.COM",
                "type": "application/rdap+json",
                "value": "https://rdap.markmonitor.com/rdap/domain/GOOGLE.COM"
            }
        ],
        "events": [
            {
                "eventDate": "1997-09-15T04:00:00Z",
                "eventAction": "registration"
            },
            {
                "eventDate": "2028-09-14T04:00:00Z",
                "eventAction": "expiration"
            },
            {
                "eventDate": "2019-09-09T15:39:04Z",
                "eventAction": "last changed"
            },
            {
                "eventDate": "2022-06-11T00:32:59Z",
                "eventAction": "last update of RDAP database"
            }
        ],
        "handle": "2138514_DOMAIN_COM-VRSN",
        "status": [
            "client delete prohibited",
            "client transfer prohibited",
            "client update prohibited",
            "server delete prohibited",
            "server transfer prohibited",
            "server update prohibited"
        ],
        "ldhName": "GOOGLE.COM",
        "notices": [
            {
                "links": [
                    {
                        "href": "https://www.verisign.com/domain-names/registration-data-access-protocol/terms-service/index.xhtml",
                        "type": "text/html"
                    }
                ],
                "title": "Terms of Use",
                "description": ["Service subject to Terms of Use."]
            },
            {
                "links": [
                    {
                        "href": "https://icann.org/epp",
                        "type": "text/html"
                    }
                ],
                "title": "Status Codes",
                "description": [
                    "For more information on domain status codes, please visit https://icann.org/epp"
                ]
            },
            {
                "links": [
                    {
                        "href": "https://icann.org/wicf",
                        "type": "text/html"
                    }
                ],
                "title": "RDDS Inaccuracy Complaint Form",
                "description": [
                    "URL of the ICANN RDDS Inaccuracy Complaint Form: https://icann.org/wicf"
                ]
            }
        ],
        "entities": [
            {
                "roles": ["registrar"],
                "handle": "292",
                "entities": [
                    {
                        "roles": ["abuse"],
                        "vcardArray": [
                            "vcard",
                            [
                                ["version", [], "text", "4.0"],
                                ["fn", [], "text", ""],
                                [
                                    "tel",
                                    {
                                        "type": "voice"
                                    },
                                    "uri",
                                    "tel:+1.2086851750"
                                ],
                                ["email", [], "text", "[email protected]"]
                            ]
                        ],
                        "objectClassName": "entity"
                    }
                ],
                "publicIds": [
                    {
                        "type": "IANA Registrar ID",
                        "identifier": "292"
                    }
                ],
                "vcardArray": [
                    "vcard",
                    [
                        ["version", [], "text", "4.0"],
                        ["fn", [], "text", "MarkMonitor Inc."]
                    ]
                ],
                "objectClassName": "entity"
            }
        ],
        "secureDNS": {
            "delegationSigned": false
        },
        "nameservers": [
            {
                "ldhName": "NS1.GOOGLE.COM",
                "objectClassName": "nameserver"
            },
            {
                "ldhName": "NS2.GOOGLE.COM",
                "objectClassName": "nameserver"
            },
            {
                "ldhName": "NS3.GOOGLE.COM",
                "objectClassName": "nameserver"
            },
            {
                "ldhName": "NS4.GOOGLE.COM",
                "objectClassName": "nameserver"
            }
        ],
        "objectClassName": "domain",
        "rdapConformance": [
            "rdap_level_0",
            "icann_rdap_technical_implementation_guide_0",
            "icann_rdap_response_profile_0"
        ]
    },
    "created_at": "2022-06-10 20:33:06"
}

Lighthouse SEO monitoring

To retrieve Lighthouse SEO reports of a site, you'll need the monitor ID first.

Getting the latest report

The /api/monitors/{$monitorId}/lighthouse-reports/latest endpoint displays that latest Lighthouse report we generated for your site.

$ OHDEAR_TOKEN="your API token"
$ curl https://ohdear.app/api/monitors/1/lighthouse-reports/latest \
    -H "Authorization: Bearer $OHDEAR_TOKEN" \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'

This will return a result like

{
    "id": 1,
    "performance_score": 50,
    "accessibility_score": 98,
    "best_practices_score": 92,
    "seo_score": 92,
    "progressive_web_app_score": 30,
    "first_contentful_paint_in_ms": 1851.45,
    "speed_index_in_ms": 1866,
    "largest_contentful_paint_in_ms": 1851.45,
    "time_to_interactive_in_ms": 2473.87,
    "total_blocking_time_in_ms": 123.77,
    "cumulative_layout_shift": 0.01,
    "performed_on_checker_server": "lighthouse-checker-frankfurt-1",
    "json_report": {
        /*
      Omitted for brevity, will contain the full JSON report generated by the lighthouse tool
      */
    },
    "issues": [
        {
            "category": "performance",
            "actualScore": 50,
            "notificationThreshold": 70
        }
    ],
    "created_at": "2023-01-14 00:00:55"
}

Getting a list of all reports

The /api/monitors/{$monitorId}/lighthouse-reports endpoint returns all Lighthouse SEO reports.

Here's some example output. Notice that we don't return the json_report in the response for this endpoint.

{
    "data": [
        {
            "id": 1423,
            "performance_score": 97,
            "accessibility_score": 98,
            "best_practices_score": 92,
            "seo_score": 92,
            "progressive_web_app_score": 30,
            "first_contentful_paint_in_ms": 1851.45,
            "speed_index_in_ms": 1866,
            "largest_contentful_paint_in_ms": 1851.45,
            "time_to_interactive_in_ms": 2473.87,
            "total_blocking_time_in_ms": 123.77,
            "cumulative_layout_shift": 0.01,
            "performed_on_checker_server": "lighthouse-checker-frankfurt-1",
            "issues": [
                {
                    "category": "performance",
                    "actualScore": 97,
                    "notificationThreshold": 99
                }
            ],
            "created_at": "2023-01-14 00:00:55"
        },
        {
            "id": 1321,
            "performance_score": 89,
            "accessibility_score": 98,
            "best_practices_score": 92,
            "seo_score": 92,
            "progressive_web_app_score": 30,
            "first_contentful_paint_in_ms": 294.65,
            "speed_index_in_ms": 328,
            "largest_contentful_paint_in_ms": 294.65,
            "time_to_interactive_in_ms": 1585.48,
            "total_blocking_time_in_ms": 445.9,
            "cumulative_layout_shift": 0.06,
            "performed_on_checker_server": "lighthouse-checker-frankfurt-1",
            "issues": [
                {
                    "category": "performance",
                    "actualScore": 89,
                    "notificationThreshold": 99
                }
            ],
            "created_at": "2023-01-12 23:38:19"
        }
    ],
    "links": {
        "first": "https://ohdear.app/api/monitors/1/lighthouse-reports?page%5Bnumber%5D=1",
        "last": "https://ohdear.app/api/monitors/1/lighthouse-reports?page%5Bnumber%5D=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "« Previous",
                "active": false
            },
            {
                "url": "https://ohdear.app/api/monitors/1/lighthouse-reports?page%5Bnumber%5D=1",
                "label": "1",
                "active": true
            },
            {
                "url": null,
                "label": "Next »",
                "active": false
            }
        ],
        "path": "https://ohdear.app/api/monitors/1/lighthouse-reports",
        "per_page": 200,
        "to": 2,
        "total": 2
    }
}

Getting a specific Lighthouse SEO report

The /api/monitors/{$monitorId}/lighthouse-reports/{$reportId} endpoint returns a specific report. You can get the $reportId by retrieving the list of Lighthouse reports (see above)

Here's an example response.

{
    "id": 1,
    "performance_score": 50,
    "accessibility_score": 98,
    "best_practices_score": 92,
    "seo_score": 92,
    "progressive_web_app_score": 30,
    "first_contentful_paint_in_ms": 1851.45,
    "speed_index_in_ms": 1866,
    "largest_contentful_paint_in_ms": 1851.45,
    "time_to_interactive_in_ms": 2473.87,
    "total_blocking_time_in_ms": 123.77,
    "cumulative_layout_shift": 0.01,
    "performed_on_checker_server": "lighthouse-checker-frankfurt-1",
    "json_report": {
        /*
      Omitted for brevity, will contain the full JSON report generated by the lighthouse tool
      */
    },
    "issues": [
        {
            "category": "performance",
            "actualScore": 50,
            "notificationThreshold": 70
        }
    ],
    "created_at": "2023-01-14 00:00:55"
}

Tags

Getting all tags

The /api/tags endpoint will return all tags for your team.

{
	"data": [
		{
			"id": 22,
			"team_id": 5,
			"team_name": "Spatie",
			"name": "production",
			"slug": "production",
			"created_at": "2025-08-15 09:00:00",
			"updated_at": "2025-08-15 09:00:00",
			"sites": [
				17,
				317
			]
		}
	]
}

Creating a tag

The /api/tags POST endpoint will create a new tag for your team with the following payload: team_id (required): The ID of your team to create the tag for. name (required): The name of the tag. sites (optional): An array of site IDs to associate with the tag.

{
	"team_id": 5,
	"name": "production",
	"sites": [17,317]
}

Now you have created a tag you can assign notification destinations to it.

Tag groups

Tag groups are a way to combine multiple tags and wildcard tags into a central place to manage notifications. For example, you could create a tag group called VIP Client Production Sites that combines the VIP, production and *client* wildcard tag.

Getting all tag groups

The /api/tag-groups endpoint will return all tag groups for your team:

{
	"data": [
		{
			"id": 6,
			"label": "VIP Client Production Sites",
			"team_id": 5,
			"team_name": "Spatie",
			"created_at": "2025-08-15 09:00:00",
			"updated_at": "2025-08-15 09:00:00",
			"tags": [
				{
					"id": 77,
					"team_id": 5,
					"team_name": "Spatie",
					"name": "VIP",
					"slug": "vip",
					"created_at": "2025-08-15 09:00:00",
					"updated_at": "2025-08-15 09:00:00",
					"sites": []
				},
				{
					"id": 78,
					"team_id": 5,
					"team_name": "Spatie",
					"name": "production",
					"slug": "production",
					"created_at": "2025-08-15 09:00:00",
					"updated_at": "2025-08-15 10:41:13",
					"sites": []
				},
				{
					"id": null,
					"team_id": 5,
					"team_name": "Spatie",
					"name": "*client*",
					"slug": "*client*",
					"created_at": null,
					"updated_at": null,
					"sites": []
				}
			]
		}
	],
	"links": {
		"first": "https://ohdear.app/api/tag-groups?page%5Bnumber%5D=1",
		"last": "https://ohdear.app/api/tag-groups?page%5Bnumber%5D=1",
		"prev": null,
		"next": null
	},
	"meta": {
		"current_page": 1,
		"from": 1,
		"last_page": 1,
		"links": [
			{
				"url": null,
				"label": "« Previous",
				"active": false
			},
			{
				"url": "https://ohdear.app/api/tag-groups?page%5Bnumber%5D=1",
				"label": "1",
				"active": true
			},
			{
				"url": null,
				"label": "Next »",
				"active": false
			}
		],
		"path": "https://ohdear.app/api/tag-groups",
		"per_page": 200,
		"to": 2,
		"total": 2
	}
}

Note: the wildcard tags have a null id property.

Creating a tag group

The /api/tag-groups POST endpoint will create a new tag group for your team with the following payload: label (required): The label of the tag group. tags (optional): An array of tag names to associate with the tag group.

{
	"team_id": 5,
	"label": "VIP Client Production Sites",
	"tags": ["VIP", "production", "*client*"]
}

Note: Any tags that do not exist will be created.

Updating a tag group

The /api/tag-groups/{$tagGroupId} PUT endpoint will update an existing tag group for your team with the following payload: label (required): The label of the tag group. tags (optional): An array of tag names to associate with the tag group.

{
	"label": "VIP Client Production Sites",
	"tags": ["VIP", "production", "*client*", "additional-tag"],
}

Note: an empty array of tags will remove all tags from the tag group. If you just want to update the label, you should omit the tags property.

Deleting a tag group

The /api/tag-groups/{$tagGroupId} DELETE endpoint will delete an existing tag group for your team.

Notification destinations

Using our API, you can retrieve notification destination of your site or teams. Because the results of these API calls can contain sensitive values, only admins and team owners are allowed to use them.

Getting the notification destinations of all your teams

The /api/team-notification-destinations endpoint will return all notification destinations defined on the team level for all your teams.

{
  "data": [
    {
      "id": 123,
      "team_id": 1,
      "channel": "mail",
      "destination": {
        "mail": "[email protected]"
      },
      "notification_types": [
        "UptimeCheckFailedNotification",
        "UptimeCheckRecoveredNotification",
        ...
      ]
    }
  ]
}

Getting the notification destinations of a site

To retrieve the notification destinations of a site, you'll need the monitor ID first.

The /api/monitors/{$monitorId}/notification-destinations endpoint will return all notification destinations for the monitor.

{
  "data": [
    {
      "id": 456,
      "channel": "mail",
      "destination": {
        "mail": "[email protected]"
      },
      "notification_types": [
        "UptimeCheckFailedNotification",
        "UptimeCheckRecoveredNotification",
        ...
      ]
    }
  ]
}

Getting the notification destinations of a tag

The /api/tags/notification-destinations endpoint will return all notification destinations defined on the tag level for all your teams.

{
  "data": [
    {
      "id": 123,
      "tag": {
        "id": 1,
        "name": "Production",
        "slug": "production"
      },
      "team_id": 1,
      "channel": "mail",
      "destination": {
        "mail": "[email protected]"
      },
      "notification_types": [
        "UptimeCheckFailedNotification",
        "UptimeCheckRecoveredNotification",
        ...
      ]
    }
  ]
}

Getting the notification destinations of a tag group

The /api/tag-groups/{$tagGroupId}/notification-destinations endpoint will return all notification destinations defined on the tag group level for all your teams.

{
	"data": [
		{
			"id": 158,
			"tagGroup": {
				"id": 1,
				"label": "VIP Client Production Sites",
				"team_id": 5,
				"team_name": "Spatie",
				"created_at": "2025-08-01 09:00:00",
				"updated_at": "2025-08-01 09:00:00",
				"tags": [
					{
						"id": 72,
						"team_id": 5,
						"team_name": "Spatie",
						"name": "VIP",
						"slug": "vip",
						"created_at": "2025-08-01 09:00:00",
						"updated_at": "2025-08-01 09:00:00",
						"sites": []
					},
          ...
				]
			},
			"channel": "slackApi",
			"destination": {
				"channel": "#monitoring",
				"slack_api_token": "xoxp-2..."
			},
			"notification_types": [
				"UptimeCheckFailedNotification",
				"UptimeCheckRecoveredNotification",
			]
		}
	]
}

Creating notification destinations

You can create a new notification destination for a monitor or team. The required payload is in the same format as the API to retrieve notification destinations which makes it easy to copy notification configurations across monitors and teams.

monitors: /api/monitors/{$monitorId}/notification-destinations

Teams: /api/team-notification-destinations/{$teamId}

Tags: /api/tags/notification-destinations/{$tagId}

Tag groups: /api/tag-groups/{$tagGroupId}/notification-destinations

Payload

{
  "channel": "mail",
  "destination": {
      "mail": "[email protected]"
   },
  "notification_types": [
      "UptimeCheckRecoveredNotification",
      "UptimeCheckFailedNotification",
   ]
}

Note: an empty array will create your configuration but will not respond to any Oh Dear events. Make sure the array has at least one item.

Payload (all events)

You can accept all supported notification types by removing the notification_types array from your request.

{
  "channel": "mail",
  "destination": {
      "mail": "[email protected]"
   }
}

Sending a test notification

You can also trigger a test notification as part of the creation process by adding test: true|false to you request body. By default this will be false.

{
  "test": true,
  "channel": "mail",
  "destination": {
      "mail": "[email protected]"
   }
}

Updating notification destinations

You can update an existing notification destination by sending a PUT/PATCH request with the updated payload to the following endpoints:

monitors: /api/monitors/{$monitorId}/notification-destinations/{$destinationId}

Teams: /api/team-notification-destinations/{$teamId}/destination/{$destinationId}

Tags: /api/tags/notification-destinations/{$tagId}/destination/{$destinationId}

Tag groups: /api/tag-groups/{$tagGroupId}/notification-destinations/{$destinationId}

{
  "test": true,
  "channel": "mail",
  "destination": {
      "mail": "[email protected]"
  }
}

You can also send test notifications on updates by adding test: true to your request body. By default this will be false.

Deleting notifications destinations#

You can delete a notification destination by sending a DELETE request to the following endpoints:

monitors: /api/monitors/{$monitorId}/notification-destinations/{$destinationId}

Teams: /api/team-notification-destinations/{$teamId}/destination/{$destinationId}

Tags: /api/tags/{$tagId}/notification-destinations/destination/{$destinationId}

Tag groups: /api/tag-groups/{$tagGroupId}/notification-destinations/{$destinationId}

No other parameters are required and a 204 response will be returned if the deletion was successful.

Channels and destinations

Each channel requires a valid destination object. You can find the supported channels and their config objects below.

Email

{
  "channel": "mail",
  "destination": {
    "mail": "[email protected]"
  },
}

Discord

{
  "channel": "discord",
  "destination": {
    "url": "https://discord.com/api/webhooks/your-webhook-url"
  }
}

Microsoft Teams

{
  "channel": "microsoftTeams",
  "destination": {
    "url": "https://teams.microsoft.com/webhook/your-webhook-url"
  }
}

Pushover

{
  "channel": "pushover",
  "destination": {
    "userKey": "your-user-key",
    "apiToken": "your-api-token",
    "priority": "0"
  }
}

Priority [lowest - emergency]: -2, -1, 0, 1, 2

Slack

{
  "channel": "slack",
  "destination": {
    "url": "https://hooks.slack.com/services/your-webhook-url"
  }
}

Slack API

{
  "channel": "slackApi",
  "destination": {
    "token": "xoxp-rest-of-your-token-here",
    "channel": "#general"
  }
}

Telegram

{
  "channel": "telegram",
  "destination": {
    "chat_id": "your-chat-id",
  }
}

SMS

{
  "channel": "sms",
  "destination": {
    "apiKey": "your-vonage-api-key",
    "apiSecret": "your-vonage-api-secret",
    "from": "your-sender-number",
    "to": "your-recipient-number"
  }
}

Webhook

{
  "channel": "webhooks",
  "destination": {
    "url": "https://your-webhook-url"
  }
}

Opsgenie

{
  "channel": "opsgenie",
  "destination": {
    "apiKey": "your-api-key",
    "euEndpoint": true,
    "priority": "P3"
  }
}

Priority: P1, P2, P3, P4, P5

Pager Duty

{
  "channel": "pagerDuty",
  "destination": {
    "apiKey": "your-service-key",
    "serviceId": "your-service-id",
    "fromEmail": "[email protected]"
  }
}

Notification types

The following notification types are valid for both teams and monitors:

  • ApplicationHealthClientErrorNotification
  • ApplicationHealthProblemDetectedNotification
  • ApplicationHealthProblemFixedNotification
  • ApplicationHealthResultsTooOldNotification
  • BrokenLinksFoundNotification
  • BrokenLinksFixedNotification
  • CertificateExpiresSoonNotification
  • CertificateFixedNotification
  • CertificateHasChangedNotification
  • CertificateUnhealthyNotification
  • CronFailedNotification
  • CronNotExecutedOnTimeNotification
  • DnsIssuesFoundNotification
  • DnsIssuesFixedNotification
  • DnsRecordsChangedNotification
  • DomainIssuesFoundNotification
  • DomainIssuesFixedNotification
  • LighthouseIssuesDetectedNotification
  • LighthouseIssuesFixedNotification
  • MixedContentFoundNotification
  • MixedContentFixedNotification
  • PerformanceDeltaExceededNotification
  • PerformanceThresholdExceededNotification
  • PerformanceThresholdRecoveredNotification
  • SitemapIssuesFoundNotification
  • SitemapIssuesFixedNotification
  • UptimeCheckFailedNotification
  • UptimeCheckRecoveredNotification

Team notification types

In addition to the generic notification types listed above the following are also valid for teams only:

<<<<<<< HEAD

  • MonitorAddedNotification =======
  • SiteAddedNotification

main

Was this page helpful?

Feel free to reach out via [email protected] or on X via @OhDearApp if you have any other questions. We'd love to help!