How to Access Your mySites.guru Account Using our Secure API

The mySites.guru MCP server lets you manage all of your WordPress and Joomla sites by talking to an AI client like Claude. A lot of you asked the obvious follow-up question: what is underneath it, and can I use that directly from my own code?
The answer is yes. Underneath the MCP server is the mySites.guru Agency API, a secure REST API that does everything the MCP server does, and it is the foundation we built the MCP server on top of. This post explains what the API is, how you connect to it securely, and why “secure by design” is a claim we can actually back up rather than a line on a marketing page.
If you have ever wanted to pull your site list into your own dashboard, fire off audits from a cron job, or trigger a round of extension updates from a deploy script, this is the tool for that.
The short version
The mySites.guru Agency API is a REST API at https://manage.mysites.guru/api/v1/ that exposes the same actions you take in the dashboard: a one-call summary of every site, every waiting update across all your sites, audit, backup and snapshot history, and the ability to start audits, backups, snapshots and extension updates. It uses OAuth2 with PKCE, no static keys, with one-hour tokens scoped to your own sites. It is the same engine the MCP server runs on, so you can use the API on its own, the MCP server on its own, or both together.
The API, the MCP server, and how they relate
The Agency API is the engine. It is a plain REST API: your code sends an HTTP request with a token, the API does the work and sends JSON back. The MCP server is a second front door bolted onto that same engine, designed so an AI client can drive it in natural language. Both share the same authentication, the same per-user scoping, and the same underlying actions. The MCP server is, in effect, a thin translation layer that turns “back up all my Joomla sites” from an AI client into the same operations the API exposes directly. The twins are easy to spot: the MCP server’s list_site_summaries, list_available_updates, get_weekly_summary, list_timeline and get_site_vulnerabilities tools read the same data as the API’s site summary, updates, weekly summary, timeline and per-site vulnerabilities endpoints.
That gives you three ways to use mySites.guru programmatically:
- The API instead of the MCP server. Write a script, a dashboard, or a scheduled job that talks straight to the REST API. No AI client involved.
- The API alongside the MCP server. Use Claude for ad-hoc, conversational work, and a scripted API integration for the repeatable, scheduled jobs. They share the same account and the same permissions model.
- The API as a standalone service. Wire mySites.guru into your own agency dashboard, a status page, or an internal tool, with no dependency on any AI tooling at all.
Pick whichever fits the job. Nothing about the API requires you to use the MCP server, and nothing about the MCP server is doing anything the API could not.
What the API can do
The API exposes the same actions you already perform by hand in the dashboard, split into reading data and triggering work.
Reading data
- Your profile: uuid, email, name and company
- Your full list of sites, with platform, version and lifecycle, PHP and database versions, tags, connection status, last audit and backup times and a count of core vulnerabilities
- A one-call summary of every site, built from data already held, with update and vulnerability counts, last snapshot, audit and backup times, SSL expiry, and a needs-attention flag with the reasons behind it; this is the endpoint that filters by platform, tag, needs-attention or a list of up to 100 hash ids (
hash_ids), and sorts by name, last snapshot, last completed backup or waiting updates. Each row also hasphp_unofficial_build, set when a site runs a PHP version above the last official release of an end-of-life branch (7.4.34, say, when PHP’s last 7.4 was 7.4.33), which means the host’s own build, whose fixes cannot be checked - A single site’s full detail, including connector version, schedules, SSL expiry, the latest core version, published core vulnerabilities and the Cloudflare zone state when you have connected Cloudflare. For Joomla it also reports
download_keys_missing, the number of enabled update sites that need a download key the site does not have (updates for those extensions fail until it is set); the keys themselves are never returned - Every known vulnerability on one site: vulnerable extensions and CMS core, both sorted worst first (critical, high, medium, low, unknown), each extension row with its name, installed version, patched version, CVE, severity, title and a
dashboard_url. Extension matches use today’s advisory rules against the extension list from the last snapshot, so a newly published advisory shows up without waiting for a new snapshot. Filter byseverity(a comma list), and sendIf-None-Matchwith the weak ETag from the last reply to get a304with no body when nothing has changed (a304still counts against the rate limit) - Every extension update waiting across all your sites in one list, each row flagged when it is a major version jump, when it has been snoozed or its site’s updates paused, and when it closes a known vulnerability (with the CVE); narrow it with
site(one site’s hash id) orextension(one exact update key) - A timeline of what ran on your sites and what your team did, newest first. Only the account owner’s token can read it; a team member’s gets a
403. Rows are grouped: one group per activity per day, one per site per day for outages and hacks, one per run of actions by a person or API client, plus release, advisory and core news rows. Pick the window withfromandto(local days, the last 7 by default, at most 90 days back) andtz(an IANA zone, UTC by default), filter byevent(snapshots, audits, backups, updates, uptime, security, alerts, extensions, ssl, cloudflare, team, releases, advisories),outcome(ok, failed, warn),trigger(you, teammate, schedule, api, mcp) orhashIdfor one site, and take 1 to 500 groups at a time (limit, default 100). Text that came from a site is never included, and a failure is explained in mySites.guru’s own words inreason - A week in review for the whole account, the same data as the weekly summary email: snapshots, audits, backups, updates, alerts and outages per day, core upgrades, the busiest sites by updates, what needs you, and the week-on-week change
- Audit results for a site (the 50 most recent), and the full detail of any individual audit
- A site’s backups and their status (the 50 most recent)
- A site’s snapshots (the 20 most recent)
- The extensions installed on a site, with their installed versions
- The tags across your account
- Your email notification switches, the same ones as the Email Notifications page, each with its current value, plus
downtimeonly(Downtime alerts only); a team member sees fewer switches than the owner, as on the page
Triggering work
- Start a new audit on a site (if another audit is already running on the same server, it waits and starts when that server is free)
- Start a new backup, with an optional comment
- Take a new snapshot
- Queue extension updates for a site, for Joomla extensions and WordPress plugins and themes alike
- Turn one email notification switch on or off, with a body like
{"key": "robotsblocked", "value": false}; an unknown key or a non-boolean value gets a422
The four job-starting endpoints return 202 Accepted immediately and run the job in the background, as they do when you click the button in the dashboard. You get a confirmation that the job was queued, not a response that blocks until a backup finishes, and the reply includes a timeline_url: a link to that site in the mySites.guru Timeline, filtered to that kind of job, where the job shows when it finishes. Changing a notification switch is the exception: it takes effect at once and the reply confirms it. Queued updates go through the same per-server Update Queue as the dashboard, so sites sharing a server update one at a time, and every one of them appears in the Update Log. A snoozed update, or one on a site with updates paused, is skipped rather than queued: the reply lists it with "skipped": "snoozed", and a call where every update named is snoozed returns 409 with the error snoozed (how snoozing works).
The important principle is that the API never grants more than you already have. Every action maps to something your account can already do. If you cannot see a site in the dashboard, no token issued to you can reach it through the API either.
How you connect: OAuth2 with PKCE
This is the part that earns the “secure” in the title, so it is worth walking through properly. If OAuth flows make your eyes glaze over, the headline is: no passwords leave your app, nothing long-lived is sitting around to be stolen, and you approve each connection yourself.
The API uses the OAuth2 Authorization Code flow with PKCE, and that is the only supported method. There are no static API keys to leak, no basic-auth passwords sitting in a config file, and no long-lived bearer tokens that work forever if they escape. If you have integrated with Google, GitHub or a bank’s API, the shape of this will be familiar. OAuth-aware clients can also discover the endpoints for themselves from the authorization server metadata and the API’s protected resource metadata, which is where a 401 response’s WWW-Authenticate header points.
Step 1: Register an API client
Open the API Clients page in your account settings and create a new client (this is the account owner’s job). You receive a client_id. There is no client_secret, because PKCE replaces it. You add the redirect URI (or URIs, one per line) your application will use, and you are done.
Step 2: Generate a PKCE pair
Before sending the user off to authorize, your application generates a one-time secret called a code verifier, and a hashed version of it called a code challenge:
// Browser example using the Web Crypto API
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const challenge = base64url(
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier))
);
You keep the verifier private and send only the challenge in the next step. This is what stops an intercepted authorization code from being useful to anyone but you.
Step 3: Send the user to authorize
Your application redirects the user to the authorization endpoint, asking for exactly the scopes it needs, with a random state value you check when the user comes back:
GET https://manage.mysites.guru/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.example.com/callback
&scope=api:profile:read api:sites:read
&code_challenge=BASE64URL_SHA256_OF_VERIFIER
&code_challenge_method=S256
&state=RANDOM_NONCE
The user arrives at a mySites.guru consent screen, signs in if they are not already, and sees exactly which permissions your application is asking for. Your application never sees their password. If they approve, they are sent back to your redirect URI with a short-lived authorization code.

Step 4: Exchange the code for tokens
Your application swaps the code, plus the original verifier, for tokens:
POST https://manage.mysites.guru/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTH_CODE_FROM_CALLBACK
&redirect_uri=https://yourapp.example.com/callback
&client_id=YOUR_CLIENT_ID
&code_verifier=YOUR_ORIGINAL_VERIFIER
You get back an access token and a refresh token:
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "eyJ...",
"refresh_token": "def502..."
}
Step 5: Call the API
From here, every request has the access token:
GET https://manage.mysites.guru/api/v1/sites
Authorization: Bearer eyJ...
And a response comes back as JSON. Every list endpoint except the timeline returns the whole set in one response, and meta tells you how many rows that was. The timeline is the one that pages: each reply has a next_before cursor and a has_more flag, and you pass the cursor back as before to read older groups.
{
"data": [
{
"hash_id": "aB3xY9",
"url": "https://acme.example.com",
"friendly_name": "Acme Corp",
"platform": "Joomla",
"version": "5.4.8",
"cms_lifecycle": "security_only",
"cms_eol_date": "2027-10-12",
"php_version": "8.3.27",
"db_version": "10.11.14-MariaDB",
"db_support_status": "supported",
"db_eol_date": "2028-02-16",
"tags": [
{ "slug": "client", "name": "Client", "color": "#3b82f6" }
],
"is_connected": true,
"last_audit": "2026-06-06T18:54:25+00:00",
"last_backup": "2026-06-06T02:00:00+00:00",
"core_vulnerability_count": 0
}
],
"meta": { "total": 87 }
}
The interactive demo does exactly this against your own account, and its redirect URI is already allowed for every API client, so you only paste in your client_id. Here it is calling GET /api/v1/sites/{hashId}/audits/{id} and getting a real audit back: files scanned, suspect files, hidden files, missing core files, and the hacked flag, all from one signed request.

Step 6: Refresh when the token expires
Access tokens last one hour. When one expires, your application uses the refresh token (valid for one month) to get a new pair, without sending the user through the consent screen again:
POST https://manage.mysites.guru/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=def502...
&client_id=YOUR_CLIENT_ID
The old refresh token is invalidated and you receive a fresh pair. Short-lived access tokens with rotating refresh tokens mean that even in the worst case, an exposed access token is useful for at most an hour.
Why “secure by design” is more than a slogan
Security here is not a feature bolted on afterwards. It falls out of how the API is built. Here is what that means in practice.
No static secrets
Because the only supported flow is Authorization Code with PKCE, there is no permanent API key that grants access forever. The thing your application holds long term is a refresh token scoped to a specific user’s approval, and even that expires. There is no shared secret to find in a leaked .env file and replay.
Tokens are scoped to your own sites, and re-checked every time
When a token is issued, the permissions are resolved for that specific user. On every single request to a site-specific endpoint, the API independently confirms that the site belongs to the user’s company, and, if the user is a restricted team member, that the site is one they are allowed to see. A site that does not exist gets a 404 not_found; a site that belongs to another account gets a 403 access_denied. A token cannot reach a site its owner could not already reach in the dashboard, and the check runs on each request rather than once at login.
Scopes mean least privilege
Your application asks only for the scopes it needs, such as api:sites:read on its own, or api:audits:write to trigger audits. The full list is api:profile:read, api:sites:read, api:sites:write (reserved, no endpoint uses it yet), api:audits:read and api:audits:write, api:backups:read and api:backups:write, api:updates:read and api:updates:write, api:tags:read, and api:preferences:read and api:preferences:write for the notification switches. The consent screen shows the user exactly what they are granting before they approve. A read-only reporting dashboard never needs to ask for write access, and a user can see that it has not. A token keeps the scopes it was issued with, so if your application later needs another one, it sends the user through the consent screen again rather than widening its reach without asking.
Short-lived tokens and per-user resolution
Access tokens expire after an hour. Permissions are resolved for the user who approved the connection, not baked into a shared client, so the same integration approved by two different people gets each person’s own access and never leaks one user’s reach to another.
Rate limited and revocable
Each access token gets 1,000 requests an hour, the hour starting at the token’s first request. Go over it and you get an HTTP 429 with a Retry-After header saying how many seconds to wait, so a runaway script is contained rather than allowed to hammer the platform. And every client you create is listed on the API Clients page, where you can revoke any of them in one click. A revoked client can no longer be authorised or refresh its tokens; an access token it already holds keeps working only until it expires, which is at most an hour away.
This is the same security model we described for the MCP server, for the simple reason that they share it. The MCP server is secure for the same reasons the API is: it is the same OAuth2 layer underneath.
What does a morning report look like through the API?
To make this concrete, picture the kind of thing the API is built for. You run an agency with 80 client sites and you want a single email each morning telling you which sites need attention, why, and which waiting updates close a known vulnerability. With the API, that is a small scheduled script built around a few calls:
- Refresh your access token if it has expired.
- Call
GET /api/v1/timeline?outcome=failed&from=YESTERDAYfor what failed overnight: every failed audit, backup, snapshot or update since yesterday, grouped by activity and day, each with the reason in mySites.guru’s own words. One request covers the whole account. - Call
GET /api/v1/sites/summary?needs_attention=true. Every site that needs action comes back in one response, each with itsattention_reasons: hacked, updates or a core update available, vulnerable extensions, core vulnerabilities, PHP missing the 21 October 2026 security release, unchecked template overrides on a Joomla 4+ site, disconnected, or a stale snapshot. Themetablock includes a one-sentence summary, such as “4 of 80 sites need attention”, and a count per reason. - Call
GET /api/v1/updatesand keep the rows whosesecurityflag is set. Each of those is an installed version with a known vulnerability that the available version fixes, named with its CVE, across every site you manage. - Optionally, queue those updates. The
extension.keyon an update row is the site’s own update key, not the key the update endpoint takes, so look each one up in that site’sGET /api/v1/sites/{hashId}/extensionslist first, then send the matchingkeyvalues toPOST /api/v1/sites/{hashId}/extensions/update. The updates go into the per-server Update Queue, so two sites on the same server never update at once, and each one shows up in the Update Log. - Email yourself the overnight failures, the
meta.summaryline and the list of security updates you queued.
That is a handful of requests whatever the number of sites you manage, which sits comfortably inside the 1,000 an hour each token gets. On a Monday, add GET /api/v1/weekly-summary for the same week in review the weekly summary email gives you: snapshots, audits, backups, updates, alerts and outages per day, and the change on the week before.
No browser, no clicking through 80 dashboards, no AI client in the loop. Just your own code, running on your own schedule, talking to a secure API. If you would rather ask the same question conversationally now and then, the MCP server answers “which of my sites need attention this morning?” from an AI client, using this exact same data.
For agencies already leaning on scheduled audits, updates and backups inside mySites.guru, the API is the natural next step when you want that automation wired into your own systems rather than ours.
What the API does not do (yet)
For most data you poll for changes. Snapshots run about every 12 hours, and the summary and the updates list change when a site is snapshotted or checked, so polling more often returns the same data. The exception is uptime: down and up alerts can be pushed to a signed webhook as they happen.
Only the timeline paginates, with the before cursor, and it reaches back about 92 days, which is how long its raw rows are kept. Every other list endpoint returns the whole set in one response, by design, and meta.total tells you how many rows that was. The installed extensions list for a site tells you what is installed and at what version, but not whether a version is out of date; that question is answered by GET /updates, which lists every waiting update across all your sites. The api:sites:write scope exists on the consent screen but no endpoint uses it yet.
The developer reference is published at manage.mysites.guru/api-docs, with a machine-readable OpenAPI spec alongside it and the interactive demo for trying calls against your own account.
If there is an operation you can do in the dashboard that you want exposed through the API, tell us. The API mirrors the dashboard, so the roadmap for it is driven by what you actually want to automate.
Getting started
- Sign in to your mySites.guru dashboard.
- Open the API Clients page in your account settings and create a client.
- Note your
client_idand add your redirect URIs, one per line. - Run the Authorization Code with PKCE flow described above against the
/oauth/authorizeand/oauth/tokenendpoints, or paste yourclient_idinto the demo to watch it happen first. - Call
https://manage.mysites.guru/api/v1/with your Bearer token, with the developer reference open beside you.
If you are not yet a mySites.guru subscriber, you can start with a free audit or see everything the platform does first. The API, like the MCP server, is there to let you bend mySites.guru around your own workflow rather than the other way around.
Further Reading
- mySites.guru API developer reference - every endpoint, scope and response shape, with the OpenAPI spec
- Every site in one API call - the site summary endpoint and its needs-attention flag
- The Update Queue: one at a time per server - where updates triggered through the API go
- Manage Every Site From Claude Desktop or other AI tools - the MCP server built on top of this API
- Schedule your security audits, updates and backups - the built-in scheduling the API complements
- How to mass upgrade Joomla and WordPress sites from one dashboard - the bulk update workflow, available to trigger via the API
- Manage multiple sites with your whole team - the team permissions model the API scoping respects
- OAuth 2.0 Authorization Framework (RFC 6749) - the standard the API is built on
- Proof Key for Code Exchange (RFC 7636) - the PKCE extension that removes the need for a client secret
Want to manage your sites without writing a line of code? The mySites.guru MCP server runs on this same API and lets you do it all in plain language from an AI client.


