Keeping a page up to date
A publish URL is a long-lived address for one page, with a token you keep in your secret store. PUT your HTML at it from a pipeline, a cron job or a script, and the page updates in place, keeping its link, its title and its version history.
Most pages are published once. Some are not: a dashboard your pipeline rebuilds every night, a report regenerated every Monday, a status page that follows a build. Re-uploading those by hand is the work, and it is the work that gets skipped.
A publish URL is a long-lived address for one page. Whatever you PUT at
it, with your publish token in the Authorization header, becomes that page's
new content. Your pipeline owns the update, and the link you already shared
keeps working.
What a publish URL is
Two parts, and only one of them is a secret.
- The address is
https://sharemypage.app/api/publish/<pageId>. That page id is the same id in the page's own link, so it is not a secret and belongs in the URL. - The token looks like
sp_pub_...and travels in theAuthorizationheader. It stands in for your account on that one page: a script holding it can replace that page's content and nothing else, so it cannot read your other pages, change your settings or create anything new.
Treat the token like a password. Anyone who has it can rewrite that page.
Where to get one
Open the page, then Settings, Content, Publish URL, and choose Create publish URL. You get the address, the token and a ready command to copy. The token is shown only at that moment.
The same section tells you when the URL was last used, and lets you replace or revoke it at any time. Only the page's owner, or a workspace admin, can see or change it.
What the token is good for, and for how long
One page, and only that one. A token is bound to the page it was issued for. Point it at a different page id and the request is refused, so a token that leaks cannot be aimed somewhere else.
You see the token once. Only a hash of it is stored, so it cannot be shown to you again, not even by us. Copy it into wherever your pipeline keeps secrets at the moment it is issued. If you lose it, issue a new one.
Revoking is immediate. Every publish re-reads whether the token is still good, so a revoked token stops working on the next request rather than at the end of some window. The page keeps the content it already has.
Creating a new token replaces the old one. A page has one publish URL at a time, so issuing a new token revokes the previous one in the same step. That is also how you recover a token you lost. Paste the new value into your secret store straight away, because any run between the two uses the old token and will be refused.
It publishes as you. Each request re-checks that you may still manage the page, so a token pointed at a page you have lost access to stops working. It does not become a standing key that outlives your own permissions.
One line to publish
curl -X PUT \
-H "Authorization: Bearer $SHAREMYPAGE_TOKEN" \
--data-binary @page.html \
https://sharemypage.app/api/publish/<pageId>That is the whole contract. No login step, no page lookup, nothing to keep in
sync. A success response is JSON with the page's url.
Store the token, not a URL
The secret you keep is the sp_pub_... token on its own. The address, page id
included, can sit in your workflow file in plain sight. That split is the reason
for the extra header: a token in a URL ends up in server logs, proxy logs and CI
job output, where it quietly outlives the run. A token in a header does not.
There is deliberately no query-parameter form of this, for the same reason.
From GitHub Actions
Put the token in Settings, Secrets and variables, Actions, as
SHAREMYPAGE_TOKEN.
name: Publish dashboard
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build the page
run: ./scripts/build-dashboard.sh > dashboard.html
- name: Publish to ShareMyPage
env:
SHAREMYPAGE_TOKEN: ${{ secrets.SHAREMYPAGE_TOKEN }}
run: |
curl --fail-with-body --silent --show-error \
-X PUT \
-H "Authorization: Bearer $SHAREMYPAGE_TOKEN" \
--data-binary @dashboard.html \
https://sharemypage.app/api/publish/<pageId>--fail-with-body is what makes the step red when a publish is refused, and
prints the reason. Without it curl exits 0 on an error response and your job
goes green on a page that never changed.
From GitLab CI
Add the token as a CI/CD variable named SHAREMYPAGE_TOKEN, with Mask
variable and Protect variable both on. Masking keeps it out of job logs;
protecting keeps it off unprotected branches, so a merge request from a fork
cannot read it.
publish:dashboard:
stage: deploy
image: curlimages/curl:latest
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- ./scripts/build-dashboard.sh > dashboard.html
- >
curl --fail-with-body --silent --show-error
-X PUT
-H "Authorization: Bearer $SHAREMYPAGE_TOKEN"
--data-binary @dashboard.html
https://sharemypage.app/api/publish/<pageId>Run it on a schedule under Build, Pipeline schedules, and the page refreshes itself on whatever rhythm the data has.
From cron or a script
No CI at all works the same way. Keep the token in a file only you can read, and have the script source it rather than carry it.
#!/usr/bin/env bash
set -euo pipefail
# chmod 600 ~/.config/sharemypage/token
source ~/.config/sharemypage/token # SHAREMYPAGE_TOKEN="sp_pub_..."
./build-report.sh > /tmp/report.html
curl --fail-with-body --silent --show-error \
-X PUT \
-H "Authorization: Bearer $SHAREMYPAGE_TOKEN" \
--data-binary @/tmp/report.html \
https://sharemypage.app/api/publish/<pageId>0 7 * * 1 /home/you/bin/publish-report.shWhat a publish changes, and what it leaves alone
Each of these is worth knowing before you point a pipeline at a page people are already reading.
The link never changes. The address you shared, the subdomain, the custom domain if you set one: all the same page afterwards. That is the point of publishing to a page you already have rather than uploading a new one.
Every push is a new version. The page's Versions tab lists each publish, so a bad build is a restore away rather than a rebuild away. How many versions are kept depends on your plan, which the FAQ spells out.
The title stays yours. The page keeps the title you gave it even when the
HTML you upload carries a different <title>. A generated file often has a
title like report-2026-11-10, and you do not want that in your nav every
night. To rename the page from a publish, say so on purpose with
?title=New%20name.
Visibility is never touched. A private page stays private, a public page stays public, a password stays in place. Publishing moves content, nothing else, so a nightly job cannot quietly widen who can read a page.
4.5 MB is the hard ceiling on one request. Our hosting platform refuses a larger request body before it reaches us, whatever your plan allows. Your plan's page-size cap applies on top of that, so the smaller of the two wins. If a generated page is bumping into it, the usual cause is inlined base64 images: host them as page images and reference them by URL instead.
Thirty publishes a minute per token. Past that a request is refused until the minute is out. A scheduled rebuild is nowhere near it; a script looping over a list of pages can be, so pace a loop rather than firing it flat out.
The page updates at once; the raw link can lag five minutes. Readers opening the page see the new content as soon as the publish returns. The tokenless raw link for a public page is cached for up to five minutes, so an AI tool reading that URL right after a push can still get the previous version. It matters when a pipeline publishes and then immediately asks an agent to read the page back: give it five minutes, or have it read the page URL rather than the raw one.
When a publish is refused
Every failure answers with a status and a reason, which is why
--fail-with-body is worth having in the command.
| Status | What happened |
|---|---|
| 400 | The body was empty, so there was nothing to publish |
| 401 | The token is missing, unknown or revoked |
| 403 | The token belongs to another page, or you no longer have access to this one |
| 404 | The page has been deleted |
| 413 | The content is over the page-size cap for your plan |
| 422 | The HTML looks truncated, so nothing was saved |
| 429 | More than 30 publishes in a minute with this token |
A request body over 4.5 MB never reaches us at all, so it fails in your job rather than coming back with one of these.
Publishing Markdown
Add ?format=markdown and the body is treated as Markdown and rendered for you,
the same as a Markdown upload anywhere else.
curl -X PUT \
-H "Authorization: Bearer $SHAREMYPAGE_TOKEN" \
--data-binary @report.md \
"https://sharemypage.app/api/publish/<pageId>?format=markdown"Useful when the thing your job produces is a report rather than a designed page: you write Markdown, the page stays styled. The Markdown you sent is kept as the page's source, so the page's own Download as Markdown still gives back what your job wrote. Without the parameter, the body is stored as HTML exactly as you sent it.
When the data is genuinely live
A publish URL suits data that is rebuilt on a schedule. If the numbers change by
the minute and a nightly push would always be stale, let the page fetch them
itself. Page content can make outbound requests, so a plain fetch() in an
inline script works, and the page is then current every time someone opens it.
Two limits decide whether that is actually an option:
- No credentials, no API keys. Anyone who can open the page can read its source, so anything in it is published. That rules out a secret, and leaves either a genuinely public endpoint or a publishable key paired with row-level security.
- The request arrives with a null origin. Page content renders in a
sandboxed frame with no origin of its own, so the far end sees
Origin: null. An endpoint answeringAccess-Control-Allow-Origin: *works; one with a domain allowlist rejects it.
Data and backends covers both in full, including what to do when a hosted service only offers a domain allowlist.
See also
- Pages for the one-off upload paths, in the browser and over MCP.
- Access and sharing for who can open a page, which a publish never changes.
- Data and backends for a page that reads live data at view time.
Images in pages
Bundle images with a page from the browser, or host them over MCP with add_page_image, and reference them by URL.
Live pages
Polls, forms, checklists and reactions turn a shared page into something readers can act on. Responses are stored for you, with no backend, no API key, and no JavaScript of your own.