Appearance
Register a site with a WP-CLI key
- Method:
POST - Path:
/v1/me/sites - Operation ID:
registerSite - Tags: Connected sites
For wp showfm connect. Binds an unregistered site key, created in show.fm within the last 24 hours, to its site. show.fm builds the showfm/v1/challenge URL from rest_root, with state as a separate query parameter, and fetches it during this request. It binds the key only if the plugin answers with code_challenge in a JSON response. Until then the key can call nothing else. The ping secret is returned once.
Scope: sites:write.
Make a request
Set these environment variables before running a sample. Each language reads the same values; no identifier needs replacing inside the code.
| Variable | Value to supply |
|---|---|
SHOWFM_API_KEY | Your API key. Keep it on your server. |
ADMIN_URL | Your WordPress admin address, on the same origin and below the home path of SITE_URL, containing /wp-admin/. Optional in the API; these examples supply it. |
SITE_URL | Your WordPress site's https address, such as https://example.com. |
REST_ROOT | A string: after normalising the host to lowercase IDNA, the normalised SITE_URL followed by /wp-json/, /?rest_route=/ or /index.php?rest_route=/. |
CONNECT_STATE | The random state the show.fm plugin stored for this connection. |
CODE_CHALLENGE | base64url(SHA-256) of the code verifier the plugin stored. |
Samples run on your server: curl 7.76+, Node.js 22+ (TypeScript via your TypeScript runner), Python 3, or PHP 8 with the cURL extension. Never put your API key in browser code.
bash
: "${SHOWFM_API_KEY:?Set SHOWFM_API_KEY in your environment}"
: "${ADMIN_URL:?Set ADMIN_URL in your environment}"
: "${SITE_URL:?Set SITE_URL in your environment}"
: "${REST_ROOT:?Set REST_ROOT in your environment}"
: "${CONNECT_STATE:?Set CONNECT_STATE in your environment}"
: "${CODE_CHALLENGE:?Set CODE_CHALLENGE in your environment}"
curl --fail-with-body --silent --show-error --request POST \
"https://api.show.fm/v1/me/sites" \
--header "Authorization: Bearer ${SHOWFM_API_KEY}" \
--header "Content-Type: application/json" \
--data "{\"admin_url\":\"${ADMIN_URL}\",\"site_url\":\"${SITE_URL}\",\"rest_root\":\"${REST_ROOT}\",\"state\":\"${CONNECT_STATE}\",\"code_challenge\":\"${CODE_CHALLENGE}\"}"ts
function env(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Set ${name} in your environment`);
return value;
}
async function main() {
const url = new URL(`https://api.show.fm/v1/me/sites`);
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${env("SHOWFM_API_KEY")}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
admin_url: env("ADMIN_URL"),
site_url: env("SITE_URL"),
rest_root: env("REST_ROOT"),
state: env("CONNECT_STATE"),
code_challenge: env("CODE_CHALLENGE")
}),
signal: AbortSignal.timeout(30_000)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
console.log(JSON.stringify(await response.json(), null, 2));
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});js
function env(name) {
const value = process.env[name];
if (!value) throw new Error(`Set ${name} in your environment`);
return value;
}
async function main() {
const url = new URL(`https://api.show.fm/v1/me/sites`);
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${env("SHOWFM_API_KEY")}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
admin_url: env("ADMIN_URL"),
site_url: env("SITE_URL"),
rest_root: env("REST_ROOT"),
state: env("CONNECT_STATE"),
code_challenge: env("CODE_CHALLENGE")
}),
signal: AbortSignal.timeout(30_000)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
console.log(JSON.stringify(await response.json(), null, 2));
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});python
import os
import json
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
from urllib.error import HTTPError
url = f'https://api.show.fm/v1/me/sites'
body = json.dumps({
"admin_url": os.environ["ADMIN_URL"],
"site_url": os.environ["SITE_URL"],
"rest_root": os.environ["REST_ROOT"],
"state": os.environ["CONNECT_STATE"],
"code_challenge": os.environ["CODE_CHALLENGE"],
}).encode()
request = Request(url, data=body, method="POST", headers={
"Authorization": "Bearer " + os.environ["SHOWFM_API_KEY"],
"Content-Type": "application/json"
})
try:
with urlopen(request, timeout=30) as response:
print(json.dumps(json.load(response), indent=2))
except HTTPError as error:
raise RuntimeError(f"HTTP {error.code}: {error.read().decode()}") from errorphp
<?php
function requiredEnv(string $name): string {
$value = getenv($name);
if ($value === false || $value === '') {
throw new RuntimeException("Set $name in your environment");
}
return $value;
}
$url = "https://api.show.fm" . "/v1/me/sites";
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . requiredEnv("SHOWFM_API_KEY"), "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"admin_url" => requiredEnv("ADMIN_URL"),
"site_url" => requiredEnv("SITE_URL"),
"rest_root" => requiredEnv("REST_ROOT"),
"state" => requiredEnv("CONNECT_STATE"),
"code_challenge" => requiredEnv("CODE_CHALLENGE"),
], JSON_THROW_ON_ERROR),
]);
$body = curl_exec($curl);
if ($body === false) {
throw new RuntimeException(curl_error($curl));
}
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($status < 200 || $status >= 300) {
throw new RuntimeException("HTTP $status: $body");
}
echo json_encode(json_decode($body, false, 512, JSON_THROW_ON_ERROR), JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR) . PHP_EOL;Base URL
https://api.show.fm
Authentication
apiKey: HTTP bearer
An API key from Settings → API keys.
showfm_live_in production.
Request body
Required: true
Content type: application/json
schema: RegisterSiteRequest, no additional properties
code_challenge(required):string, pattern:^[A-Za-z0-9_-]{43}$The S256 challenge: base64url of the SHA-256 of the plugin verifier. show.fm fetches the plugin challenge for
stateand must get this back.rest_root(required):string, minLength:1, maxLength:2048WordPress rest_url(): after normalising the host to lowercase IDNA, exactly the normalised site_url plus /wp-json/, /?rest_route=/ or /index.php?rest_route=/. The path keeps its case.
site_url(required):stringThe site's https address, with no query, fragment, port or login details.
state(required):string, pattern:^[A-Za-z0-9_-]{32,128}$A fresh random value the plugin stored before calling, 32 to 128 URL-safe characters.
admin_url:string, minLength:1, maxLength:2048Optional WordPress admin address for reconnect links. Must be https, on the same origin as site_url, under its home path and contain /wp-admin/, with no credentials, port or fragment. Stored as the plugin settings address. Omit to preserve an existing address; older connections without one use the site home path plus /wp-admin/.
Responses
Browse the documented response statuses, headers and body fields.
201 Created
Response headers
RateLimit-PolicyThe bucket that applied, e.g.
"account";q=60;w=60.stringRateLimitRequests left and seconds to reset, e.g.
"account";r=59;t=42.string
Response body
Content type: application/json
no additional properties
data(required): RegisteredSite
400, 401, 403, 404, 500, 503
400invalid_request: a bad body (seeerror.details), or a key sent in the URL.401unauthorized(no Bearer key) orinvalid_api_key.403insufficient_scope,forbidden(role or team-key setting) orplan_upgrade_required.404not_found: the key cannot reach this resource.500internal_error. Retry; quoteX-Request-Idif it persists.503service_unavailable. Retry shortly.
Response body
Content type: application/json
schema: Error, no additional properties
error(required):object, no additional propertiescode(required):string, possible values:"invalid_request", "not_found", "unavailable", "unauthorized", "invalid_api_key", "insufficient_scope", "forbidden", "plan_upgrade_required", "rate_limited", "service_unavailable", "internal_error", "not_implemented"message(required):stringdetails:arrayItems:
no additional properties
field(required):stringmessage(required):stringreason:stringA stable reason a client may branch on, for example
outside_retention.
docs_url:stringrequest_id:string
429 rate_limited. Wait for Retry-After seconds.
rate_limited. Wait for Retry-After seconds. Response headers
Retry-AfterintegerRateLimit-PolicyThe bucket that applied, e.g.
"account";q=60;w=60.stringRateLimitRequests left and seconds to reset, e.g.
"account";r=59;t=42.string
Response body
Content type: application/json
Uses the shared Error schema. View response fields.