AppForShare

The Diawi API with curl and Node, and how to point it elsewhere

· 7 min read

The Diawi API with curl and Node, and how to point it elsewhere

Diawi has no official CLI or CI plugin, but it has a small upload API, and a lot of build scripts call it directly. The API is two calls: send the file and get a job id, then ask for the job status until the link is ready. This guide shows both calls with curl and with Node, explains the fields, and then shows how to send the same script to another service by changing one URL. Diawi details come from its plans page, read on November 2, 2026, and from the field list used by community clients. Diawi's own API reference sits behind the dashboard sign in, so check field names there when in doubt.

Before you start

  • Plan. Diawi lists API access from Starter (EUR 2.99 a month) up. The free account does not include it. See Diawi limits.
  • Token. Create an API token in the Diawi dashboard. Keep it in your CI secret store, not in the script.
  • File. An .ipa signed Ad Hoc, Development or Enterprise, a .zip holding a .app, or a signed .apk. Not an .aab.

Step 1: upload the file

The upload is a multipart POST to https://upload.diawi.com/. Only token and file are required.

curl -s https://upload.diawi.com/ \
  -F token="$DIAWI_TOKEN" \
  -F [email protected] \
  -F comment="Build 142: new checkout screen" \
  -F password="tester-only" \
  -F callback_emails="[email protected]"

The answer is JSON with a job id. The file is processed after the upload, so the link is not ready yet.

The optional fields

FieldWhat it does
passwordTesters enter it before installing
commentA note shown to testers
callback_emailsEmails the link: 1 address on the free account, up to 5 on paid plans
callback_urlAn address told when the upload is processed
installation_notificationsEmail on each install (paid)
find_by_udidLets testers find the app by their device
wall_of_appsShows the app on Diawi's public wall

Step 2: poll the job status

Ask https://upload.diawi.com/status with the token and the job id until the status is final. Three codes matter:

  • 2000: done, the answer carries the link details.
  • 2001: still processing, ask again in a few seconds.
  • 4000: failed, the message says why.

A complete shell script with jq:

#!/usr/bin/env bash
set -euo pipefail
UPLOAD_URL="${UPLOAD_URL:-https://upload.diawi.com/}"
STATUS_URL="${STATUS_URL:-https://upload.diawi.com/status}"
TOKEN="${DIAWI_TOKEN:?set DIAWI_TOKEN}"

JOB=$(curl -sf "$UPLOAD_URL" -F token="$TOKEN" -F file=@"$1" \
  -F comment="${2:-}" | jq -r .job)

for i in $(seq 1 60); do
  R=$(curl -sf "$STATUS_URL?token=$TOKEN&job=$JOB")
  case $(echo "$R" | jq -r .status) in
    2000) echo "$R" | jq -r .link; exit 0 ;;
    2001) sleep 5 ;;
    *)    echo "$R" | jq -r .message >&2; exit 1 ;;
  esac
done
echo "Timed out waiting for job $JOB" >&2; exit 1

Run it as ./share.sh app-release.ipa "Build 142". The upload and status addresses are variables on purpose; that is what makes the switch below a one line change. Note that the token travels in the query string of the status call, so keep CI logs from printing full URLs.

The same thing in Node

Node 20 and later have fetch, FormData and openAsBlob built in, so no packages are needed:

import { openAsBlob } from 'node:fs';
import { basename } from 'node:path';

const UPLOAD_URL = process.env.UPLOAD_URL ?? 'https://upload.diawi.com/';
const STATUS_URL = process.env.STATUS_URL ?? 'https://upload.diawi.com/status';
const token = process.env.DIAWI_TOKEN;
const file = process.argv[2];

const form = new FormData();
form.set('token', token);
form.set('file', await openAsBlob(file), basename(file));
form.set('comment', process.argv[3] ?? '');

const { job } = await (await fetch(UPLOAD_URL, { method: 'POST', body: form })).json();

for (let i = 0; i < 60; i++) {
  const url = `${STATUS_URL}?token=${encodeURIComponent(token)}&job=${job}`;
  const r = await (await fetch(url)).json();
  if (r.status === 2000) { console.log(r.link); process.exit(0); }
  if (r.status !== 2001) { console.error(r.message); process.exit(1); }
  await new Promise((ok) => setTimeout(ok, 5000));
}
console.error(`Timed out waiting for job ${job}`);
process.exit(1);

If you prefer a package, the community diawi-nodejs-uploader on npm wraps both calls, and it accepts apiUploadEndpoint and apiStatusEndpoint overrides.

Running it in CI

Keep the token in the CI secret store and pass it as an environment variable. On GitHub Actions:

- name: Share with testers
  run: ./share.sh build/App.ipa "${{ github.event.head_commit.message }}"
  env:
    DIAWI_TOKEN: ${{ secrets.DIAWI_TOKEN }}

The same pattern works on GitLab (a masked CI/CD variable), Bitrise (a secret), CircleCI (a context or project variable) and Jenkins (a Secret text credential with withCredentials). If you used the community Jenkins plugin for this, read the Diawi Jenkins plugin advisory first.

When the upload fails

  • No job id comes back. Print the raw answer instead of piping it to jq. A wrong or expired token, or a plan without API access, is the usual cause.
  • Status 4000. Read the message. Likely reasons are a file type Diawi does not accept, such as an .aab, or a file above your plan's size limit (20 MB free, 50 MB Starter, 250 MB Premium).
  • The link works but the install fails. That is iOS refusing the build, not the API. Check that the iPhone is in the profile: see integrity could not be verified.
  • The file field is empty. In curl, the @ before the path is what attaches the file. Without it, curl sends the path as text.

Pointing the same script at AppForShare

AppForShare, the tool we make, has a Diawi compatible endpoint on Pro and Team. It takes Diawi's multipart field names and answers in Diawi's shape, so a script like the ones above switches by changing the URL and the token:

export UPLOAD_URL=https://appforshare.io/api/ci/diawi
export STATUS_URL=https://appforshare.io/api/ci/diawi/status
export DIAWI_TOKEN=afs_your_appforshare_token
./share.sh app-release.ipa "Build 142"

How the fields map:

Diawi fieldAt AppForShare
tokenYour AppForShare API token (Settings, API tokens). An Authorization: Bearer header also works.
file.ipa, .zip holding a .app, .apk or .aab (turned into one APK)
passwordPassword on the build, 4 to 128 characters
commentThe build's release notes on the install page
callback_emailsUp to 5 addresses, each gets one plain email with the install link
callback_urlCalled once when the build is ready (2000) or failed (4000), with the same fields as the status call
installation_notifications, find_by_udid, wall_of_appsAccepted and ignored

The status call answers with the same codes, 2000, 2001 and 4000. When the build is ready it includes hash (the build's link code), link (the install page) and qrcode (a QR image address). The callback is tried once with no retries, so treat the status call as the reliable answer.

Two limits to know. One request can carry up to 100 MB. For bigger builds, use the AppForShare CLI, npx appforshare upload, which sends files in resumable 32 MB parts, up to your plan's file size (2 GB on Pro, 4 GB on Team). And the endpoint accepts up to 30 uploads an hour from one address.

The endpoint also accepts one field Diawi does not have: channel, the name of a channel such as Beta. The build then goes into that channel when it is ready, and the channel's stable link opens it. On Pro you can give the channel a readable address such as /c/acme-beta.

Once you have switched, you can also add things Diawi's API does not have, such as putting the build into a channel with a stable link, or failing the CI job when the store readiness check finds a blocker. The CLI and the full API are documented at CI API reference and CI uploads, and the step by step move is in Move from Diawi.

Common questions

Does the Diawi API work on the free account?

As of November 2, 2026, Diawi lists API access from Starter up.

How long should I poll?

Polling every 5 seconds for up to 5 minutes, as in the scripts above, is a reasonable default. Raise the limit for very large builds.

Can I use the Diawi compatible endpoint on AppForShare Free?

No. It needs an API token, which is part of Pro and Team.

Do I have to change my fastlane or GitHub Actions setup too?

If they call a Diawi plugin rather than your own script, switch them to the official AppForShare fastlane plugin or GitHub Action. See moving off Diawi.

Keep reading

App Distribution Platform · · 9 min read

AppForShare.io vs Competitors - A Comparative Analysis

For small teams and startups, selecting a mobile app testing platform is a critical crossroad. This choice is more than a mere selection: it’s a strategic decision that directly impacts the...