Settings

Shop settings, integrations, domain, SMTP

GET /admin/api/shop-info

Returns the shop name. No authentication required -- used by the admin login page.

GET /admin/api/settings

Returns current shop settings including name, contact email, theme colour, language, currency, tax, and country configuration.

PATCH /admin/api/settings

Update shop settings. The name and theme_color fields are required; all others use COALESCE semantics (omit to keep existing value).

GET /admin/api/settings/log-404s

Returns whether storefront 404s are being recorded into the not-found log.

PUT /admin/api/settings/log-404s

Enable or disable recording of storefront 404s. Existing log entries are unaffected when the toggle is flipped off; the storefront simply stops appending new ones.

GET /admin/api/available-languages

Returns the full list of languages available for selection. Each entry has a BCP 47 code and a native display name.

GET /admin/api/custom-domain

Returns the shop's current custom domain, or null if none is configured.

PUT /admin/api/custom-domain

Set or clear the shop's custom domain. Must contain at least one dot. Must not be a subdomain of the platform domain. Send null to clear. Requires a Live-tier shop — Preview shops receive 402 with `error: tier_limit_exceeded`, `reason: custom_domain_requires_live_tier`. Clearing the domain (setting null) is always allowed. See the Tenant Tier reference.

GET /admin/api/custom-scripts

Returns the current custom JavaScript/HTML snippets injected into the storefront.

PUT /admin/api/custom-scripts

Update the custom scripts injected into the storefront's head and body.

GET /admin/api/smtp

Returns current SMTP settings. The password is never returned -- only a has_password boolean.

PATCH /admin/api/smtp

Update SMTP settings. Password is encrypted at rest with AES-256-GCM. Send null for password to preserve existing. When enabling, host, sender_email, and password are required.

POST /admin/api/smtp/test

Send a test email to the shop's contact email address using the saved SMTP settings. SMTP must be enabled and fully configured.

GET /admin/api/integrations

List all configured integrations (payment gateways, marketing tools, shipping providers), ordered by category then provider.

GET /admin/api/integrations/{provider}

Get a single integration by provider name.

PUT /admin/api/integrations/{provider}

Create or update an integration. Cannot disable the last active payment method.

POST /admin/api/integrations/stripe-connect/create-account

Create a Stripe Express Connected Account for this shop. Only one account can be connected at a time.

POST /admin/api/integrations/stripe-connect/onboarding-link

Generate a Stripe Account Link for merchant onboarding. The merchant is redirected to Stripe to enter business details and bank info.

GET /admin/api/integrations/stripe-connect/status

Refresh the connected Stripe account's status by calling the Stripe API. Updates cached charges_enabled and details_submitted flags.

POST /admin/api/integrations/stripe-connect/disconnect

Remove the connected Stripe account. The account is not deleted on Stripe's side. Cannot disconnect if it is the last active payment method.

POST /admin/api/integrations/easyship/test

Test the Easyship API connection by sending a minimal rates request with the configured API key and origin address.

GET /admin/api/marketing/channels

Returns first-touch and last-touch revenue/order counts grouped by UTM source, UTM campaign, and HTTP referrer. Used for the order-attribution report. Test orders are excluded.

GET /admin/api/seo/static-pages

List SEO meta for all eight built-in storefront routes.

GET /admin/api/seo/static-pages/{route}

Fetch SEO meta for a single built-in route.

PUT /admin/api/seo/static-pages/{route}

Set SEO meta for a built-in route. Empty strings fall back to template defaults; pass `og_image_media_id: null` to clear.

GET /admin/api/seo/static-pages/{route}/translations

List per-locale overrides for a static page's meta_title and meta_description.

PUT /admin/api/seo/static-pages/{route}/translations

Set per-locale overrides for meta_title and meta_description. Replaces all overrides for the route.

GET /admin/api/custom-domain/status

Returns the current custom-domain provisioning status as a tagged object keyed by `state` (one of none, pending_dns, pending_ssl, active, failed). For any non-none state the payload includes the CNAME records to add at the registrar and the raw Cloudflare ssl_status. Same shape and permission as GET /admin/api/custom-domain; exists so the setup wizard can poll while the domain is in a non-terminal state.

POST /admin/api/settings/favicon

Upload the shop favicon. Multipart/form-data with a single `file` field (PNG or JPEG, source ≤ 2 MB). Generates 32px and 180px PNGs plus stores the source, then returns cache-busted public URLs. Tier-gated by per-file size only.

POST /admin/api/settings/checkout-logo

Upload the checkout-page logo. Multipart/form-data with a single `file` field (PNG or JPEG, source ≤ 2 MB). The image is downscaled to a PNG, stored, and the cache-busted public URL returned. Tier-gated by per-file size only.

POST /admin/api/integrations/stripe-connect/dashboard-link

Create a one-time Stripe Express Dashboard login link for the connected account. Redirect the merchant to the returned URL to view payouts and balances on Stripe's hosted dashboard. The link is single-use and short-lived; request it on demand.

GET /admin/api/apple-pay/status

Returns the cached Apple Pay domain-registration status from the registry without calling Stripe. Cheap enough to call on every Payments page render. status is one of active, inactive, pending, or null when no registration exists; status_reason carries Stripe's reason when not active.

POST /admin/api/apple-pay/recheck

Force a fresh Stripe round-trip to re-register and re-check the Apple Pay domain under the per-tenant reconcile lock, then return the refreshed cached row. Powers the SPA's "Re-check" button. Errors from the underlying reconcile are logged and swallowed -- the response always returns whatever the registry currently holds, so the same payload shape as GET status is returned even if Stripe is momentarily down. Takes no body.

GET /admin/api/shop-info

Returns the shop name. No authentication required -- used by the admin login page.

Response 200

{
  "name": "My Awesome Shop"
}

Example

curl "https://yourshop.zeroshop.io/admin/api/shop-info"
GET /admin/api/settings

Returns current shop settings including name, contact email, theme colour, language, currency, tax, and country configuration.

Response 200

{
  "name": "My Awesome Shop",
  "contact_email": "hello@myshop.com",
  "theme_color": "#3b82f6",
  "default_language": "en",
  "supported_languages": [
    "en",
    "sv"
  ],
  "currency_code": "USD",
  "currency_symbol": "$",
  "prices_include_tax": false,
  "allowed_countries": [
    "US",
    "CA",
    "GB"
  ],
  "tax_provider": "builtin"
}

Example

curl "https://yourshop.zeroshop.io/admin/api/settings" \
  -H "Authorization: Bearer zspat_..."
PATCH /admin/api/settings

Update shop settings. The name and theme_color fields are required; all others use COALESCE semantics (omit to keep existing value).

Request Body

{
  "name": "My Awesome Shop",
  "contact_email": "hello@myshop.com",
  "theme_color": "#3b82f6",
  "default_language": "en",
  "supported_languages": [
    "en",
    "sv"
  ],
  "currency_code": "USD",
  "currency_symbol": "$",
  "prices_include_tax": false,
  "allowed_countries": [
    "US",
    "CA"
  ],
  "tax_provider": "builtin"
}

Response 204

Example

curl -X PATCH "https://yourshop.zeroshop.io/admin/api/settings" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "My Awesome Shop", "theme_color": "#3b82f6", "currency_code": "USD", "currency_symbol": "$"}'
GET /admin/api/settings/log-404s

Returns whether storefront 404s are being recorded into the not-found log.

Response 200

{
  "enabled": true
}

Example

curl "https://yourshop.zeroshop.io/admin/api/settings/log-404s" \
  -H "Authorization: Bearer zspat_..."
PUT /admin/api/settings/log-404s

Enable or disable recording of storefront 404s. Existing log entries are unaffected when the toggle is flipped off; the storefront simply stops appending new ones.

Request Body

{
  "enabled": true
}

Response 200

{
  "enabled": true
}

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/settings/log-404s" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'
GET /admin/api/available-languages

Returns the full list of languages available for selection. Each entry has a BCP 47 code and a native display name.

Response 200

[
  {
    "code": "en",
    "name": "English"
  },
  {
    "code": "sv",
    "name": "Svenska"
  },
  {
    "code": "de",
    "name": "Deutsch"
  },
  {
    "code": "fr",
    "name": "Français"
  }
]

Example

curl "https://yourshop.zeroshop.io/admin/api/available-languages" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/custom-domain

Returns the shop's current custom domain, or null if none is configured.

Response 200

{
  "domain": "shop.acme.com"
}

Example

curl "https://yourshop.zeroshop.io/admin/api/custom-domain" \
  -H "Authorization: Bearer zspat_..."
PUT /admin/api/custom-domain

Set or clear the shop's custom domain. Must contain at least one dot. Must not be a subdomain of the platform domain. Send null to clear. Requires a Live-tier shop — Preview shops receive 402 with `error: tier_limit_exceeded`, `reason: custom_domain_requires_live_tier`. Clearing the domain (setting null) is always allowed. See the Tenant Tier reference.

Request Body

{
  "domain": "shop.acme.com"
}

Response 200

{
  "domain": "shop.acme.com"
}

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/custom-domain" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"domain": "shop.acme.com"}'
GET /admin/api/custom-scripts

Returns the current custom JavaScript/HTML snippets injected into the storefront.

Response 200

{
  "head_scripts": "<!-- Google Analytics -->\n<script async src=\"https://www.googletagmanager.com/gtag/js?id=G-XXXXX\"></script>",
  "body_scripts": ""
}

Example

curl "https://yourshop.zeroshop.io/admin/api/custom-scripts" \
  -H "Authorization: Bearer zspat_..."
PUT /admin/api/custom-scripts

Update the custom scripts injected into the storefront's head and body.

Request Body

{
  "head_scripts": "<script async src=\"https://www.googletagmanager.com/gtag/js?id=G-XXXXX\"></script>",
  "body_scripts": ""
}

Response 204

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/custom-scripts" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"head_scripts": "<script>...</script>", "body_scripts": ""}'
GET /admin/api/smtp

Returns current SMTP settings. The password is never returned -- only a has_password boolean.

Response 200

{
  "enabled": true,
  "host": "smtp.mailgun.org",
  "port": 587,
  "username": "postmaster@mg.myshop.com",
  "has_password": true,
  "encryption": "starttls",
  "sender_email": "noreply@myshop.com",
  "sender_name": "My Awesome Shop"
}

Example

curl "https://yourshop.zeroshop.io/admin/api/smtp" \
  -H "Authorization: Bearer zspat_..."
PATCH /admin/api/smtp

Update SMTP settings. Password is encrypted at rest with AES-256-GCM. Send null for password to preserve existing. When enabling, host, sender_email, and password are required.

Request Body

{
  "enabled": true,
  "host": "smtp.mailgun.org",
  "port": 587,
  "username": "postmaster@mg.myshop.com",
  "password": "secret-smtp-password",
  "encryption": "starttls",
  "sender_email": "noreply@myshop.com",
  "sender_name": "My Awesome Shop"
}

Response 204

Example

curl -X PATCH "https://yourshop.zeroshop.io/admin/api/smtp" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "host": "smtp.mailgun.org", "port": 587, "encryption": "starttls", "sender_email": "noreply@myshop.com"}'
POST /admin/api/smtp/test

Send a test email to the shop's contact email address using the saved SMTP settings. SMTP must be enabled and fully configured.

Response 200

{
  "ok": true,
  "message": "Test email sent to hello@myshop.com"
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/smtp/test" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/integrations

List all configured integrations (payment gateways, marketing tools, shipping providers), ordered by category then provider.

Response 200

[
  {
    "id": 1,
    "provider": "stripe-connect",
    "category": "payments",
    "enabled": true,
    "sandbox": false,
    "config": "{\"account_id\":\"acct_1234\",\"charges_enabled\":true,\"details_submitted\":true}",
    "created_at": "2025-10-01T10:00:00Z",
    "updated_at": "2025-11-15T09:00:00Z"
  },
  {
    "id": 2,
    "provider": "manual",
    "category": "payments",
    "enabled": true,
    "sandbox": false,
    "config": "{}",
    "created_at": "2025-10-01T10:00:00Z",
    "updated_at": "2025-10-01T10:00:00Z"
  }
]

Example

curl "https://yourshop.zeroshop.io/admin/api/integrations" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/integrations/{provider}

Get a single integration by provider name.

Parameters

Name Type Required Description
provider string required Provider name (e.g. "stripe-connect", "easyship", "manual")

Response 200

{
  "id": 1,
  "provider": "stripe-connect",
  "category": "payments",
  "enabled": true,
  "sandbox": false,
  "config": "{\"account_id\":\"acct_1234\",\"charges_enabled\":true,\"details_submitted\":true}",
  "created_at": "2025-10-01T10:00:00Z",
  "updated_at": "2025-11-15T09:00:00Z"
}

Example

curl "https://yourshop.zeroshop.io/admin/api/integrations/stripe-connect" \
  -H "Authorization: Bearer zspat_..."
PUT /admin/api/integrations/{provider}

Create or update an integration. Cannot disable the last active payment method.

Parameters

Name Type Required Description
provider string required Provider name (e.g. "easyship", "manual")

Request Body

{
  "category": "payments",
  "enabled": true,
  "sandbox": false,
  "config": {
    "api_key": "sk_live_..."
  }
}

Response 200

{
  "id": 1,
  "provider": "easyship",
  "category": "shipping",
  "enabled": true,
  "sandbox": false,
  "config": "{\"api_key\":\"sk_live_...\"}",
  "created_at": "2025-10-01T10:00:00Z",
  "updated_at": "2025-11-20T14:00:00Z"
}

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/integrations/easyship" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"category": "shipping", "enabled": true, "sandbox": false, "config": {"api_key": "sk_live_..."}}'
POST /admin/api/integrations/stripe-connect/create-account

Create a Stripe Express Connected Account for this shop. Only one account can be connected at a time.

Response 200

{
  "id": 1,
  "provider": "stripe-connect",
  "category": "payments",
  "enabled": true,
  "sandbox": true,
  "config": "{\"account_id\":\"acct_1N7abc\",\"charges_enabled\":false,\"details_submitted\":false}",
  "created_at": "2025-11-20T14:00:00Z",
  "updated_at": "2025-11-20T14:00:00Z"
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/integrations/stripe-connect/create-account" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/integrations/stripe-connect/onboarding-link

Generate a Stripe Account Link for merchant onboarding. The merchant is redirected to Stripe to enter business details and bank info.

Request Body

{
  "return_url": "https://yourshop.zeroshop.io/admin/settings/integrations",
  "refresh_url": "https://yourshop.zeroshop.io/admin/settings/integrations"
}

Response 200

{
  "url": "https://connect.stripe.com/setup/s/acct_1N7abc/abc123"
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/integrations/stripe-connect/onboarding-link" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"return_url": "https://yourshop.zeroshop.io/admin/settings", "refresh_url": "https://yourshop.zeroshop.io/admin/settings"}'
GET /admin/api/integrations/stripe-connect/status

Refresh the connected Stripe account's status by calling the Stripe API. Updates cached charges_enabled and details_submitted flags.

Response 200

{
  "id": 1,
  "provider": "stripe-connect",
  "category": "payments",
  "enabled": true,
  "sandbox": true,
  "config": "{\"account_id\":\"acct_1N7abc\",\"charges_enabled\":true,\"details_submitted\":true}",
  "created_at": "2025-11-20T14:00:00Z",
  "updated_at": "2025-11-21T10:00:00Z"
}

Example

curl "https://yourshop.zeroshop.io/admin/api/integrations/stripe-connect/status" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/integrations/stripe-connect/disconnect

Remove the connected Stripe account. The account is not deleted on Stripe's side. Cannot disconnect if it is the last active payment method.

Response 200

{
  "id": 1,
  "provider": "stripe-connect",
  "category": "payments",
  "enabled": false,
  "sandbox": true,
  "config": "{}",
  "created_at": "2025-11-20T14:00:00Z",
  "updated_at": "2025-11-22T09:00:00Z"
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/integrations/stripe-connect/disconnect" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/integrations/easyship/test

Test the Easyship API connection by sending a minimal rates request with the configured API key and origin address.

Response 200

{
  "ok": true,
  "message": "Connection successful."
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/integrations/easyship/test" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/marketing/channels

Returns first-touch and last-touch revenue/order counts grouped by UTM source, UTM campaign, and HTTP referrer. Used for the order-attribution report. Test orders are excluded.

Parameters

Name Type Required Description
from string required Start of the date range, inclusive. ISO 8601 YYYY-MM-DD.
to string required End of the date range, inclusive. ISO 8601 YYYY-MM-DD.

Response 200

{
  "first_touch": {
    "by_source": [
      {
        "key": "google",
        "orders": 42,
        "revenue_cents": 158400
      },
      {
        "key": "newsletter",
        "orders": 11,
        "revenue_cents": 39900
      },
      {
        "key": null,
        "orders": 7,
        "revenue_cents": 21000
      }
    ],
    "by_campaign": [
      {
        "key": "spring-sale",
        "orders": 18,
        "revenue_cents": 72000
      }
    ],
    "by_referrer": [
      {
        "key": "https://www.google.com/",
        "orders": 30,
        "revenue_cents": 110000
      }
    ]
  },
  "last_touch": {
    "by_source": [
      {
        "key": "google",
        "orders": 51,
        "revenue_cents": 189500
      },
      {
        "key": null,
        "orders": 6,
        "revenue_cents": 18000
      }
    ],
    "by_campaign": [
      {
        "key": "spring-sale",
        "orders": 22,
        "revenue_cents": 87600
      }
    ],
    "by_referrer": [
      {
        "key": "https://www.google.com/",
        "orders": 35,
        "revenue_cents": 128000
      }
    ]
  }
}

Example

curl "https://yourshop.zeroshop.io/admin/api/marketing/channels?from=2026-01-01&to=2026-01-31" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/seo/static-pages

List SEO meta for all eight built-in storefront routes.

Response 200

{
  "data": [
    {
      "route": "home",
      "meta_title": "Welcome to Acme",
      "meta_description": "Hand-curated goods, shipped worldwide.",
      "og_image_media_id": 14,
      "og_image_url": "https://cdn.zeroshop.io/acme/og-home.webp",
      "updated_at": "2026-04-30T18:22:00Z"
    },
    {
      "route": "cart",
      "meta_title": "",
      "meta_description": "",
      "og_image_media_id": null,
      "og_image_url": null,
      "updated_at": "2026-04-30T18:22:00Z"
    }
  ]
}

Example

curl "https://yourshop.zeroshop.io/admin/api/seo/static-pages" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/seo/static-pages/{route}

Fetch SEO meta for a single built-in route.

Parameters

Name Type Required Description
route string required One of: "home", "cart", "checkout", "search", "account", "login", "register", "not_found".

Response 200

{
  "route": "home",
  "meta_title": "Welcome to Acme",
  "meta_description": "Hand-curated goods, shipped worldwide.",
  "og_image_media_id": 14,
  "og_image_url": "https://cdn.zeroshop.io/acme/og-home.webp",
  "updated_at": "2026-04-30T18:22:00Z"
}

Example

curl "https://yourshop.zeroshop.io/admin/api/seo/static-pages/home" \
  -H "Authorization: Bearer zspat_..."
PUT /admin/api/seo/static-pages/{route}

Set SEO meta for a built-in route. Empty strings fall back to template defaults; pass `og_image_media_id: null` to clear.

Request Body

{
  "meta_title": "Welcome to Acme",
  "meta_description": "Hand-curated goods, shipped worldwide.",
  "og_image_media_id": 14
}

Response 200

{
  "route": "home",
  "meta_title": "Welcome to Acme",
  "meta_description": "Hand-curated goods, shipped worldwide.",
  "og_image_media_id": 14,
  "og_image_url": "https://cdn.zeroshop.io/acme/og-home.webp",
  "updated_at": "2026-04-30T18:25:00Z"
}

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/seo/static-pages/home" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"meta_title":"Welcome to Acme","meta_description":"Hand-curated goods.","og_image_media_id":14}'
GET /admin/api/seo/static-pages/{route}/translations

List per-locale overrides for a static page's meta_title and meta_description.

Response 200

{
  "data": [
    {
      "locale": "de",
      "meta_title": "Willkommen bei Acme",
      "meta_description": "Handverlesene Produkte, weltweiter Versand."
    },
    {
      "locale": "fr",
      "meta_title": "Bienvenue chez Acme",
      "meta_description": "Produits triés sur le volet, expédiés dans le monde entier."
    }
  ]
}

Example

curl "https://yourshop.zeroshop.io/admin/api/seo/static-pages/home/translations" \
  -H "Authorization: Bearer zspat_..."
PUT /admin/api/seo/static-pages/{route}/translations

Set per-locale overrides for meta_title and meta_description. Replaces all overrides for the route.

Request Body

{
  "translations": [
    {
      "locale": "de",
      "meta_title": "Willkommen bei Acme",
      "meta_description": "Handverlesene Produkte, weltweiter Versand."
    },
    {
      "locale": "fr",
      "meta_title": "Bienvenue chez Acme",
      "meta_description": "Produits triés sur le volet."
    }
  ]
}

Response 204

Example

curl -X PUT "https://yourshop.zeroshop.io/admin/api/seo/static-pages/home/translations" \
  -H "Authorization: Bearer zspat_..." \
  -H "Content-Type: application/json" \
  -d '{"translations":[{"locale":"de","meta_title":"Willkommen","meta_description":"…"}]}'
GET /admin/api/custom-domain/status

Returns the current custom-domain provisioning status as a tagged object keyed by `state` (one of none, pending_dns, pending_ssl, active, failed). For any non-none state the payload includes the CNAME records to add at the registrar and the raw Cloudflare ssl_status. Same shape and permission as GET /admin/api/custom-domain; exists so the setup wizard can poll while the domain is in a non-terminal state.

Response 200

{
  "state": "pending_ssl",
  "hostname": "shop.acme.com",
  "primary_record": {
    "kind": "CNAME",
    "name": "shop.acme.com",
    "value": "acme.zeroshop.io"
  },
  "dcv_record": {
    "kind": "CNAME",
    "name": "_acme-challenge.shop.acme.com",
    "value": "shop.acme.com.abcdef.dcv.cloudflare.com"
  },
  "ssl_status": "pending_validation",
  "last_error": null,
  "last_synced_at": "2026-05-29T10:00:00Z"
}

Example

curl "https://yourshop.zeroshop.io/admin/api/custom-domain/status" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/settings/favicon

Upload the shop favicon. Multipart/form-data with a single `file` field (PNG or JPEG, source ≤ 2 MB). Generates 32px and 180px PNGs plus stores the source, then returns cache-busted public URLs. Tier-gated by per-file size only.

Request Body

{
  "file": "<binary PNG or JPEG>"
}

Response 200

{
  "favicon_url": "https://cdn.zeroshop.io/acme/favicon/180.png?v=1717063200",
  "icon_url": "https://cdn.zeroshop.io/acme/favicon/32.png?v=1717063200",
  "apple_url": "https://cdn.zeroshop.io/acme/favicon/180.png?v=1717063200",
  "has_favicon": true
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/settings/favicon" \
  -H "Authorization: Bearer zspat_..." \
  -F "file=@favicon.png"
POST /admin/api/integrations/stripe-connect/dashboard-link

Create a one-time Stripe Express Dashboard login link for the connected account. Redirect the merchant to the returned URL to view payouts and balances on Stripe's hosted dashboard. The link is single-use and short-lived; request it on demand.

Response 200

{
  "url": "https://connect.stripe.com/express/acct_1N7abc/abc123login"
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/integrations/stripe-connect/dashboard-link" \
  -H "Authorization: Bearer zspat_..."
GET /admin/api/apple-pay/status

Returns the cached Apple Pay domain-registration status from the registry without calling Stripe. Cheap enough to call on every Payments page render. status is one of active, inactive, pending, or null when no registration exists; status_reason carries Stripe's reason when not active.

Response 200

{
  "domain": "shop.acme.com",
  "status": "pending",
  "status_reason": "missing_payment_method_domain_file"
}

Example

curl "https://yourshop.zeroshop.io/admin/api/apple-pay/status" \
  -H "Authorization: Bearer zspat_..."
POST /admin/api/apple-pay/recheck

Force a fresh Stripe round-trip to re-register and re-check the Apple Pay domain under the per-tenant reconcile lock, then return the refreshed cached row. Powers the SPA's "Re-check" button. Errors from the underlying reconcile are logged and swallowed -- the response always returns whatever the registry currently holds, so the same payload shape as GET status is returned even if Stripe is momentarily down. Takes no body.

Response 200

{
  "domain": "shop.acme.com",
  "status": "active",
  "status_reason": null
}

Example

curl -X POST "https://yourshop.zeroshop.io/admin/api/apple-pay/recheck" \
  -H "Authorization: Bearer zspat_..."

We value your privacy

We use cookies for essential site functionality and, with your consent, analytics to understand how our platform is used. No personal data is shared with third parties. See our Privacy Policy for details.