API reference

Publish updates and change your Store listing from CI, a script or your own tools. The API does what Publish an update and the Store listing page do on the dashboard.

Every endpoint is under https://storefast.app/api/v1, takes and returns JSON, and works on the apps of the account that created the key. The API comes with the Indie and Studio plans and the free trial.

Working in Claude Code, Cursor or Codex? The MCP server gives your agent the same steps as tools.

Authentication

Create a key in Settings under API keys, and send it with every request.

Authorization: Bearer sf_...
  • Read only keys can list your apps, check on a release and read your Store listing.
  • Can publish keys can also start, upload, submit and cancel releases, and change and send your Store listing.

You see a key once. StoreFast keeps only a hash, so if you lose one, revoke it and create another. Keep keys in your CI's secrets, never in the repository.

A key can't change your store connection or billing. Responses never include your Partner Center credentials or upload addresses.

Errors

An error comes back as { "error": "..." } with a message you can show to a person.

400
A field is missing or has a bad value.
401
The key is missing, wrong or revoked.
403
The key is read only, your plan doesn't include the API, or your plan doesn't cover this app.
404
The app or release isn't in your account.
409
The release can't do that right now, like submitting before the upload is finished, or Partner Center already has a submission waiting.
413
The request body is over 1 MB. Part uploads don't have this limit.
429
Too many requests, or today's limit for AI writing is used up.
5xx
Something failed on our side or at Microsoft. Try again in a minute.

Limits

  • Each key can make 120 requests a minute. Past that you get a 429 with a Retry-After header in seconds. Part uploads count, so a 4000 MB package takes about four minutes.
  • Packages can be up to 4000 MB.
  • Release notes can be up to 1,500 characters per language.
  • Each app has one release in progress at a time.
  • Translations and summaries share a daily limit with the dashboard that resets at midnight UTC. Sending the same English again is free.
  • You can have up to 20 keys.

Publish a release

A release goes through the same steps as on the dashboard:

  1. Start a release with the version, file name and size.
  2. Upload the package in parts.
  3. Finish the upload.
  4. Save the release notes. StoreFast translates them.
  5. Submit the release.
  6. Check the release until Partner Center has it.

Nothing reaches Partner Center until you submit, so you can cancel any time before that. A release you start here also shows on the app's page as an unfinished update. The full script is at the end of this page.

Endpoints

GET/apps

Any key

Lists your apps with the version in the Store and the status of a submission in certification.

Request
curl -H "$AUTH" $API/apps
Response
{
  "apps": [
    {
      "id": "3f2b7c1e-8a4d-4e6b-9c2f-5d1a0b7e6c94",
      "name": "My App",
      "storeAppId": "9NBLGGH4R315",
      "liveVersion": "1.3.0.0",
      "pendingStatus": null
    }
  ]
}

POST/apps/:appId/releases

Key that can publish

Starts a release.

versionstring
Four numbers, higher than the version in the Store, like 1.4.0.0.
fileNamestring
The package's file name. Ends in .msix.
sizenumber
The file size in bytes, up to 4000 MB.
Request
curl -X POST -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"version":"1.4.0.0","fileName":"MyApp_1.4.0.0_x64.msix","size":29606598}' \
  $API/apps/$APP_ID/releases
Response
{
  "releaseId": "7d0c5a2e-41b8-4f3a-a6e9-2b8c1f4d9e07",
  "partSize": 8388608,
  "partCount": 4
}

If the app already has a release in progress, you get a 409 with its releaseId. Continue that release or cancel it.

PUT/releases/:releaseId/parts/:partNumber

Key that can publish

Uploads one part of the package as raw bytes. Number the parts from 1 to partCount. Each is partSize bytes except the last. Send them in any order or in parallel. Sending a part again replaces it.

Request
curl -X PUT -H "$AUTH" --data-binary @part-aa $API/releases/$RELEASE/parts/1
Response
{ "ok": true }

POST/releases/:releaseId/finish

Key that can publish

Completes the upload and returns your listing's languages with their current release notes. You can call it again safely.

Request
curl -X POST -H "$AUTH" $API/releases/$RELEASE/finish
Response
{
  "languages": [
    { "code": "en", "releaseNotes": "- Live transcription for recordings." },
    { "code": "fr", "releaseNotes": "- Transcription en direct pour les enregistrements." }
  ]
}

POST/releases/:releaseId/notes

Key that can publish

Saves the release notes and translates them into your listing's other languages.

englishstring
The English notes, up to 1,500 characters. English listing languages get these as written.
translateboolean
Translates the other languages in your app's notes format and writing style. Defaults to true.
notesobject
Notes for specific languages by language code, used as written.
summarizeboolean
Rewrites long notes, like a changelog, into a short What's new of about 1,150 characters first. english can then be up to 20,000 characters. Defaults to false.
platform"windows"
With summarize, leaves out changes that only affect other platforms.
Request
curl -X POST -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"english":"- Starts faster.","translate":false,"notes":{"fr":"- Démarre plus vite."}}' \
  $API/releases/$RELEASE/notes
Response
{
  "english": "- Starts faster.",
  "summarized": false,
  "notes": { "en": "- Starts faster.", "fr": "- Démarre plus vite." },
  "untranslated": ["de", "es"]
}

The response's english is what was saved. Languages in untranslated get the English when you submit.

Summarize a changelog
curl -X POST -H "$AUTH" -H 'Content-Type: application/json' \
  --data "$(jq -n --rawfile notes CHANGELOG-1.4.0.md '{english: $notes, summarize: true, platform: "windows"}')" \
  $API/releases/$RELEASE/notes

POST/releases/:releaseId/submit

Key that can publish

Sends the package and notes to Partner Center and submits them for certification.

notesobject, optional
Notes by language code. Without it, the saved notes are used. A language without notes gets the English.
holdPublishboolean, optional
After certification, the update waits until you click Publish now in Partner Center. Without it, the update keeps the publish setting of your last submission.
Request
curl -X POST -H "$AUTH" $API/releases/$RELEASE/submit
Response
{ "status": "submitting" }
Hold until you publish
curl -X POST -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"holdPublish": true}' $API/releases/$RELEASE/submit

GET/releases/:releaseId

Any key

Returns where the release is.

statusstring
uploading, uploaded, submitting, submitted, failed or canceled.
errorstring or null
What went wrong, when the status is failed.
partnerCenterUrlstring or null
The submission's page in Partner Center, once it's submitted.
certificationobject
Partner Center's status, like PreProcessing, Certification or Published, and any errors. waitingForYou is true when it passed certification and waits for you to publish it in Partner Center. Included once Partner Center has the submission. It updates for a few minutes after you submit, then with each daily sync.
Request
curl -H "$AUTH" $API/releases/$RELEASE
Response
{
  "status": "submitted",
  "version": "1.4.0.0",
  "error": null,
  "partnerCenterUrl": "https://partner.microsoft.com/dashboard/products/9NBLGGH4R315/submissions/1152921505699345678/",
  "certification": { "status": "PreProcessing", "errors": [], "waitingForYou": false }
}

DELETE/releases/:releaseId

Key that can publish

Cancels a release that hasn't been submitted and deletes the uploaded package. Canceling a release that's already canceled or failed is fine.

Request
curl -X DELETE -H "$AUTH" $API/releases/$RELEASE
Response
{ "ok": true }

Store listing

Listing changes are saved as the same draft the Store listing page edits. The next release you submit carries them, or you can send them on their own.

GET/apps/:appId/listing

Any key

Returns the listing with the saved changes applied: each language's title, short description and search terms, which fields the draft changes, the notes for certification and the problems that would stop it from sending. Problems with level error block sending; warnings don't. Add ?language=en-us for one language's full text and its screenshots.

Request
curl -H "$AUTH" "$API/apps/$APP_ID/listing?language=en-us"

PATCH/apps/:appId/listing

Key that can publish

Saves changes to the draft. Send only what changes. Returns the listing as above.

languagesobject
Fields by language code: title, shortDescription, description, features and keywords. Lists replace the whole list. A field set back to its live text stops being a change.
addLanguagesstring[]
Store language codes to add. New languages need a description and a new set of screenshots.
removeLanguagesstring[]
Languages added in the draft to take out again.
screenshotsarray or null
Screenshot ids from the upload in the order the Store shows them, each with optional captions by language code. They replace the live screenshots in every language. null keeps the live ones.
captionsobject
Captions by language code, in screenshot order: on the new screenshots when there are some, otherwise on that language's live screenshots.
certNotesstring
Notes for certification, up to 2,000 characters. They go out with every submission.
Request
curl -X PATCH -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"languages":{"en-us":{"keywords":["note taking","markdown notes"]}},"captions":{"en-us":["Find any note by searching"]}}' \
  $API/apps/$APP_ID/listing

PUT/apps/:appId/screenshots

Key that can publish

Uploads one screenshot as a raw PNG, from 1366 x 768 up to 3840 x 2160 and at most 50 MB. Returns its id for screenshots above. Screenshots stay private until they're sent.

Request
curl -X PUT -H "$AUTH" --data-binary @home.png $API/apps/$APP_ID/screenshots
Response
{ "id": "51b2ebad-2fe4-4b0f-9bc2-566838a11005", "width": 1920, "height": 1080, "size": 284113 }

POST/apps/:appId/listing/send

Key that can publish

Sends the saved listing changes to Partner Center for certification without a new package. It answers 400 while the listing has problems that block sending. Check on it with GET /releases/:releaseId. It takes holdPublish like Submit.

Request
curl -X POST -H "$AUTH" $API/apps/$APP_ID/listing/send
Response
{ "releaseId": "0f6d2c3a-9b1e-4c57-8a2d-7e4b1c9f3a60", "status": "submitting" }

Example: publish from a script

The whole flow in one shell script, using curl and jq.

publish.sh
export STOREFAST_API_KEY=sf_...
API=https://storefast.app/api/v1
AUTH="Authorization: Bearer $STOREFAST_API_KEY"
APP_ID=...                        # from GET /apps
FILE=MyApp_1.4.0.0_x64.msix
SIZE=$(wc -c < "$FILE" | tr -d ' ')

# 1. Start the release
RELEASE=$(curl -s -X POST -H "$AUTH" -H 'Content-Type: application/json' \
  -d "{\"version\":\"1.4.0.0\",\"fileName\":\"$FILE\",\"size\":$SIZE}" \
  $API/apps/$APP_ID/releases | jq -r .releaseId)

# 2. Upload the package in 8 MiB parts, numbered from 1
split -b 8m "$FILE" part-
n=1
for part in part-*; do
  curl -sf -X PUT -H "$AUTH" --data-binary @"$part" $API/releases/$RELEASE/parts/$n || exit 1
  n=$((n + 1))
done
rm part-*

# 3. Finish the upload and read the listing languages
curl -s -X POST -H "$AUTH" $API/releases/$RELEASE/finish

# 4. Write the notes; the other languages are translated for you
curl -s -X POST -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"english":"- Starts faster.\n- Fixes a crash when closing the window."}' \
  $API/releases/$RELEASE/notes

# 5. Submit for certification
curl -s -X POST -H "$AUTH" $API/releases/$RELEASE/submit

# 6. Wait until the package is with Microsoft
while true; do
  STATUS=$(curl -s -H "$AUTH" $API/releases/$RELEASE | jq -r .status)
  echo "$STATUS"
  [ "$STATUS" = submitting ] || break
  sleep 15
done

Stuck on something? Email hello@storefast.app and we'll help you get it working.