amplifysignal.comamplifysignal.com →

Finding the correct Ghost Admin API URL and authenticating requests

The correct Ghost Admin API URL is always the .ghost.io subdomain, even when your blog uses a custom domain. Learn how to format the URL, split your API key, generate the required HS256 JWT, and successfully authenticate your requests.

·6 min read

The correct ghost admin api url for a hosted site is always its .ghost.io subdomain, even when the public blog uses a custom domain. Sending an authenticated request to a custom domain returns a 403 error. You must direct your API calls to the .ghost.io address and authenticate them using a short-lived JSON Web Token signed with your Admin API key.

Why does the custom domain return a 403 error?

The custom domain returns a 403 error because Ghost routes admin traffic strictly through the .ghost.io subdomain assigned to your account. When you set up a custom domain for a Ghost(Pro) installation, the public-facing website serves traffic through that new address. It is natural to assume the API endpoints live on the same domain. If your site is example.com, you might send a POST request to https://example.com/ghost/api/admin/posts/.

This request will fail. The server will reject it with a 403 Forbidden status code and the exact message: Unable to determine the authenticated user or integration.

The routing layer refuses Admin API requests made to the custom domain, regardless of whether your authentication token is perfectly formed. To fix the 403 error, you must replace the custom domain in your request with the admin domain. The correct path looks like https://yoursite.ghost.io/ghost/api/admin/posts/.

Do API responses still use the custom domain?

Yes, the API responses include URLs formatted with your site's configured custom domain, even when you send the request to the .ghost.io endpoint. Routing your requests to the admin domain raises an immediate question about the data you get back. If you ask the API to create a new post, you need the public URL of that post to share it on social networks or store it in your own database.

You do not need to worry about the .ghost.io domain leaking into your public links. When you send a request to the Ghost Admin API, the JSON response fully respects your site configuration. If you create a published post, the url property in the returned JSON object contains the fully qualified custom domain. The canonical URL and any absolute links generated within the post content also use the custom domain. The .ghost.io address operates strictly as an entry point for administrative commands and does not overwrite your public site identity.

How do you format the Ghost Admin API key?

You format the API key by splitting the single string provided in the dashboard at the colon to separate the ID from the secret. The Ghost administration panel provides this key when you create a new custom integration.

The format is id:secret. The section before the colon is your key ID, and the section after the colon is your secret. You cannot use this string directly in an authorization header. You must programmatically split the string at the colon and store the two halves in separate variables to generate your authentication token.

How do you generate the JWT for authentication?

How do you generate the JWT for authentication?

You generate the JSON Web Token by signing a short-lived payload with the hex-decoded secret half of your API key using the HS256 algorithm. Static bearer tokens or long-lived API keys passed in the URL are not supported.

The JWT consists of a header and a payload. The header requires a kid property, which stands for Key ID. You must set this property to the ID half of your split API key.

The payload requires three specific claims to pass validation. First, the aud or audience claim must be set exactly to the string '/admin/'. Second, the iat or issued-at claim must be set to the current Unix timestamp in seconds. Third, the exp or expiration claim must be set to a future timestamp. Ghost limits the lifetime of an Admin API token to a maximum of five minutes. Setting the exp value to the current timestamp plus 300 seconds satisfies this requirement.

The signature step requires careful handling and is the most common source of authentication errors. Most JWT libraries expect the signing secret to be a raw string or a base64-encoded string. The secret half of the Ghost API key is a hex-encoded string. If you pass the hex string directly into your signing function, the resulting token will have an invalid signature and fail authentication. You must decode the hex string into a raw byte array first, and then use that byte array to sign the token payload.

What headers does the Ghost Admin API require?

The API requires an Authorization header containing your generated token and an Accept-Version header specifying the expected API version. Once you have generated and signed the JWT, you must include it in your HTTP request.

Instead of the standard Bearer prefix used by many APIs, Ghost requires a custom scheme. You must format the header as Authorization: Ghost <token>, replacing the token placeholder with your generated JWT.

You must also include an Accept-Version header. This tells the server which version of the API structure you expect to send and receive. Ghost maintains backward compatibility for its API endpoints. Passing Accept-Version: v5.0 is the standard approach for integrations. I verified this against Ghost(Pro) version 6.62, and the v5.0 header still works perfectly against Ghost 6 environments.

How do you handle API failures when publishing?

You handle API failures by generating a fresh token before every request and querying the post slug before retrying to prevent duplicate content. Tokens expire exactly at the five-minute mark. Network latency or slight clock differences between your server and Ghost's servers can cause a token to be rejected if you generate it right at the expiration boundary.

When an API call times out or returns a 500-level error, you have no immediate way of knowing if the post was successfully saved to the database before the connection dropped. Blindly retrying a POST request to the /admin/posts/ endpoint can result in multiple identical articles. Refer to Handling partial syndication failures without creating duplicate posts for the mechanics of idempotent retries. To prevent duplicates in Ghost, you query the API using GET /admin/posts/?filter=slug:your-expected-slug before retrying. If the post exists, you switch your retry from a POST creation to a PUT update.

How do you structure the POST payload?

How do you structure the POST payload?

You structure the payload by wrapping your article object inside an array assigned to a top-level posts key. Ghost expects a highly specific JSON wrapper for creating and updating content.

The article object requires a title and a content property. You can supply content as mobiledoc, lexical, or html. Supplying HTML is the most straightforward approach for external integrations. Ghost automatically parses the HTML and converts it into its internal document format.

To schedule a post for future publication, set the status property to scheduled and provide a published_at property. The publication time must be formatted as an ISO 8601 timestamp. The API enforces a strict buffer rule for scheduled content. The timestamp must be at least two minutes in the future relative to the server time, or the API will reject the request. Keeping an eye on your scheduling horizons helps prevent these errors. You can read more about planning those horizons in Finding the right draft buffer size vs publication velocity for solo developers.

How do you test the API connection?

You test the connection by sending a GET request to the site configuration endpoint to verify your routing and token generation without modifying data. Before writing the logic to generate complete article payloads, you should verify your authentication code.

Send your request to https://yoursite.ghost.io/ghost/api/admin/site/, including your Authorization and Accept-Version headers. This endpoint returns basic configuration data about your Ghost installation. It requires full authentication but does not modify any state.

If you receive a 200 OK response, your target URL, split API key, hex-decoded signature, and headers are functioning correctly. If you receive a 401 Unauthorized response, inspect your token expiration logic and ensure you are hex-decoding the secret before signing. If you receive a 403 Forbidden response, verify that you are sending the request to the .ghost.io domain instead of your custom domain.

How do you automate drafted articles?

You can automate the process using software that handles token generation and API routing to schedule posts directly. Writing the code to hex-decode secrets, format JWT headers, and manage strict API validation rules takes time away from actual publishing.

I built AmplifySignal to handle these integration details for you. It takes a target keyword, drafts a complete article based on your standing instructions, and authenticates directly with your Ghost installation to schedule the post, ensuring the correct admin routing and token protocols are used every time.