Skip to main content
Getly

Command menu

Jump to a page or search the catalog

API

API Documentation

Complete reference for the Getly Developer API v1

Authentication

All API requests must include your API key in the Authorization header using the Bearer token scheme.

HTTP header
Authorization: Bearer gk_your_api_key_here

Available Scopes

API keys are scoped to a specific store and have granular permissions. Create keys at /dashboard/developer/keys.

read:productswrite:productsread:ordersread:storewrite:storeread:analyticswebhooks:manageread:postswrite:postsread:couponswrite:couponsread:licenseswrite:licensescheckout:createread:billingwrite:billing

Rate Limits

API requests are rate limited to 100 requests per minute per API key. If you exceed the limit, you will receive a 429 response - wait a short moment and retry.

Plan your integrations to stay within these limits. Batch operations where possible and cache responses on your end.

Error Handling

All API responses follow a consistent format. Errors include a descriptive message.

application/json
// Success
{ "success": true, "data": { ... } }

// Error
{
  "success": false,
  "error": "Description of what went wrong",
  "errorDetail": {
    "code": "validation_failed",   // stable, safe to branch on
    "message": "price must be a positive integer in cents",
    "hint": "Fix the field named in `param` and retry the same request.",
    "param": "price",              // omitted when no single field is at fault
    "docsUrl": "https://www.getly.store/developers/docs#errors"
  }
}

HTTP Status Codes

200 - Success

201 - Created

400 - Bad Request (invalid input)

401 - Unauthorized (missing/invalid API key)

403 - Forbidden (missing scope or not your resource)

404 - Not Found

500 - Internal Server Error

Endpoints

Base URL: https://www.getly.store. All prices are in cents (e.g. 1999 = $19.99).

GET/api/v1/productsread:products

List products for your store

bash
curl -X GET "https://www.getly.store/api/v1/products?page=1&limit=20" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "name": "Premium Icon Pack",
      "slug": "premium-icon-pack-abc123",
      "price": 1999,
      "status": "active",
      "category": { "id": "uuid", "name": "Design", "slug": "design" },
      "images": [{ "url": "...", "altText": "..." }]
    }
  ],
  "total": 42,
  "page": 1,
  "limit": 20,
  "hasMore": true
}
POST/api/v1/productswrite:products

Create a new product in your store

bash
curl -X POST "https://www.getly.store/api/v1/products" \
  -H "Authorization: Bearer gk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My New Product",
    "description": "A great digital product",
    "price": 29.99,
    "categoryId": "uuid",
    "status": "active",
    "tags": ["design", "icons"],
    "images": [{ "url": "https://cdn.getly.store/...png", "altText": "cover" }],
    "files": [{ "fileUrl": "https://cdn.getly.store/files/.../abc.zip", "fileName": "pack.zip", "fileSize": 10485760, "fileType": "application/zip" }]
  }'
Response · application/json
{
  "success": true,
  "data": {
    "id": "uuid",
    "name": "My New Product",
    "slug": "my-new-product-abc123",
    "price": 2999,
    "status": "active",
    "createdAt": "2025-01-15T10:30:00Z"
  }
}
GET/api/v1/products/:idread:products

Get product details by ID

bash
curl -X GET "https://www.getly.store/api/v1/products/{id}" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": {
    "id": "uuid",
    "name": "Premium Icon Pack",
    "price": 1999,
    "images": [...],
    "files": [...],
    "reviews": [...]
  }
}
PATCH/api/v1/products/:idwrite:products

Update an existing product

bash
curl -X PATCH "https://www.getly.store/api/v1/products/{id}" \
  -H "Authorization: Bearer gk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Updated Name", "price": 39.99 }'
Response · application/json
{
  "success": true,
  "data": { "id": "uuid", "name": "Updated Name", "price": 3999 }
}
DELETE/api/v1/products/:idwrite:products

Archive (soft-delete) a product. It stops being publicly listed; existing orders and downloads keep working.

bash
curl -X DELETE "https://www.getly.store/api/v1/products/{id}" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": { "id": "uuid", "status": "archived" },
  "message": "Product archived"
}
POST/api/v1/products/:id/fileswrite:products

Attach a downloadable file to a product. Use multipart for small files, or the presign sub-route + a direct R2 PUT for files up to 2GB. You can also pass a files[] array to POST /api/v1/products to attach at creation time.

bash
# Direct upload (multipart) - best for files under ~4MB:
curl -X POST "https://www.getly.store/api/v1/products/{id}/files" \
  -H "Authorization: Bearer gk_your_api_key" \
  -F "file=@/path/to/your-file.zip"

# Large files (up to 2GB) - 3 steps:
# 1) get a presigned upload URL
curl -X POST "https://www.getly.store/api/v1/products/{id}/files/presign" \
  -H "Authorization: Bearer gk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "fileName": "pack.zip", "fileSize": 524288000, "fileType": "application/zip" }'
# 2) PUT the bytes straight to R2 (use the returned uploadUrl)
curl -X PUT "<uploadUrl>" --data-binary "@/path/to/pack.zip" \
  -H "Content-Length: 524288000"
# 3) attach the uploaded object (use the returned fileUrl)
curl -X POST "https://www.getly.store/api/v1/products/{id}/files" \
  -H "Authorization: Bearer gk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "fileUrl": "<fileUrl>", "fileName": "pack.zip", "fileSize": 524288000, "fileType": "application/zip" }'
Response · application/json
{
  "success": true,
  "data": {
    "id": "uuid",
    "fileName": "pack.zip",
    "fileUrl": "https://cdn.getly.store/files/.../abc.zip",
    "fileSize": 524288000,
    "fileType": "application/zip",
    "version": "1.0",
    "isLatest": true,
    "createdAt": "2026-06-02T10:30:00Z"
  }
}
POST/api/v1/uploads/images/presignwrite:products

Get a presigned URL to upload a product image (max 10MB; png, jpeg, webp, gif, avif). Required before a product can go live.

bash
# Images are uploaded in 3 steps - there is no direct multipart route.
# 1) ask for a presigned URL (max 10MB; png, jpeg, webp, gif or avif)
curl -X POST "https://www.getly.store/api/v1/uploads/images/presign" \
  -H "Authorization: Bearer gk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "fileName": "cover.png", "fileSize": 184320, "contentType": "image/png" }'

# 2) PUT the raw bytes to the returned uploadUrl (expires in 1h).
#    Content-Length MUST equal the fileSize you declared.
curl -X PUT "<uploadUrl>" --data-binary "@/path/to/cover.png" \
  -H "Content-Type: image/png" -H "Content-Length: 184320"

# 3) reference the returned publicUrl when you create or update the product.
#    An image is REQUIRED before a product can go live.
curl -X PATCH "https://www.getly.store/api/v1/products/{id}" \
  -H "Authorization: Bearer gk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "images": ["<publicUrl>"] }'
Response · application/json
{
  "success": true,
  "data": {
    "uploadUrl": "https://<bucket>.r2.cloudflarestorage.com/...",
    "publicUrl": "https://cdn.getly.store/images/.../abc.png",
    "expiresIn": 3600
  }
}
GET/api/v1/products/:id/keysread:products

List a product's own license-key pool: counters (available/issued/waiting) and a masked, paginated list. The plaintext key is never returned.

bash
curl -X GET "https://www.getly.store/api/v1/products/{id}/keys?limit=50&offset=0" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": {
    "keyPoolEnabled": true,
    "keyPoolLowThreshold": 5,
    "counts": { "available": 12, "issued": 40, "waiting": 0 },
    "keys": [
      { "id": "uuid", "masked": "ABCD…WXYZ", "status": "available", "issuedAt": null, "orderItemId": null, "refunded": false }
    ],
    "limit": 50,
    "offset": 0,
    "total": 52
  }
}
POST/api/v1/products/:id/keyswrite:products

Paste your own keys, vouchers or unique URLs into a product's pool, one per array element. Duplicates and blanks are reported, not silently dropped; buyers who were waiting get a key at once.

bash
curl -X POST "https://www.getly.store/api/v1/products/{id}/keys" \
  -H "Authorization: Bearer gk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "keys": ["ABCD-1234-EFGH-5678", "IJKL-9012-MNOP-3456"] }'
Response · application/json
{
  "success": true,
  "data": {
    "added": 2,
    "duplicates": 0,
    "duplicatesInInput": 0,
    "tooLong": 0,
    "blank": 0,
    "filled": 1
  }
}
DELETE/api/v1/products/:id/keys/:keyIdwrite:products

Remove an unsold key from the pool. An issued key is a sale record and cannot be removed - 409 key_not_available.

bash
curl -X DELETE "https://www.getly.store/api/v1/products/{id}/keys/{keyId}" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": { "id": "uuid", "removed": true }
}
GET/api/v1/ordersread:orders

List orders containing items from your store

bash
curl -X GET "https://www.getly.store/api/v1/orders?page=1&limit=20" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "orderId": "uuid",
      "price": 2999,
      "order": { "status": "completed", "buyer": { "name": "John" } },
      "product": { "name": "Premium Pack" }
    }
  ],
  "total": 156,
  "page": 1
}
GET/api/v1/orders/:idread:orders

Get order details (only items from your store)

bash
curl -X GET "https://www.getly.store/api/v1/orders/{id}" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": {
    "id": "uuid",
    "status": "completed",
    "total": 5999,
    "buyer": { "name": "John", "email": "[email protected]" },
    "items": [...]
  }
}
GET/api/v1/storeread:store

Get your store information

bash
curl -X GET "https://www.getly.store/api/v1/store" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": {
    "id": "uuid",
    "name": "My Store",
    "slug": "my-store",
    "description": "...",
    "totalSales": 250,
    "totalRevenue": 450000,
    "badges": [...]
  }
}
PATCH/api/v1/storewrite:store

Update store name, description, website, socialLinks

bash
curl -X PATCH "https://www.getly.store/api/v1/store" \
  -H "Authorization: Bearer gk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "description": "Updated store description" }'
Response · application/json
{
  "success": true,
  "data": { "id": "uuid", "name": "My Store", "description": "Updated store description" }
}
GET/api/v1/analyticsread:analytics

Get sales analytics for your store

bash
curl -X GET "https://www.getly.store/api/v1/analytics" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": {
    "totalSales": 250,
    "totalRevenue": 450000,
    "monthlySales": 42,
    "monthlyRevenue": 75000,
    "averageOrderValue": 1800,
    "productCount": 15,
    "totalDownloads": 1200,
    "salesByMonth": [
      { "month": "2025-01", "sales": 38, "revenue": 68000 }
    ]
  }
}
GET/api/v1/webhook-endpointswebhooks:manage

List webhook endpoints for your store

bash
curl -X GET "https://www.getly.store/api/v1/webhook-endpoints" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "uuid",
        "url": "https://example.com/webhooks",
        "events": ["sale.completed"],
        "isActive": true
      }
    ],
    "nextCursor": null
  }
}
POST/api/v1/webhook-endpointswebhooks:manage

Register a new webhook endpoint

bash
curl -X POST "https://www.getly.store/api/v1/webhook-endpoints" \
  -H "Authorization: Bearer gk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks",
    "events": ["sale.completed", "product.updated"]
  }'
Response · application/json
{
  "success": true,
  "data": {
    "id": "uuid",
    "url": "https://example.com/webhooks",
    "events": ["sale.completed", "product.updated"],
    "secret": "abcdef1234567890..."
  }
}
POST/api/v1/billing/planswrite:billing

Create a recurring plan for your OWN product (Getly Billing). amount in integer cents (min 50), intervalUnit day/week/month/year, intervalCount 1–52. Requires an approved Billing application - 403 billing_not_approved until then. GET lists plans; GET/PATCH /{id} reads and edits one.

bash
curl -X POST "https://www.getly.store/api/v1/billing/plans" \
  -H "Authorization: Bearer gk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Pro", "amount": 1900, "intervalUnit": "month", "externalId": "pro-monthly" }'
Response · application/json
{
  "success": true,
  "data": {
    "id": "uuid",
    "name": "Pro",
    "amount": 1900,
    "currency": "usd",
    "interval": { "unit": "month", "count": 1 },
    "externalId": "pro-monthly",
    "isActive": true
  }
}
POST/api/v1/billing/checkoutwrite:billing

Mint a hosted subscribe page for one customer, valid 24 hours. customerRef is your own id for the customer and comes back on every billing.* webhook as externalCustomerRef. The customer pays by card (renews by itself) or crypto (one period at a time).

bash
curl -X POST "https://www.getly.store/api/v1/billing/checkout" \
  -H "Authorization: Bearer gk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "planId": "uuid", "customerRef": "user_8841",
        "successUrl": "https://your-app.com/welcome",
        "cancelUrl": "https://your-app.com/pricing" }'
Response · application/json
{
  "success": true,
  "data": {
    "url": "https://www.getly.store/billing/subscribe/uuid?t=…",
    "expiresAt": "2026-09-09T09:00:00.000Z"
  }
}
GET/api/v1/billing/subscriptions/{id}read:billing

The status check your backend makes before granting access. status: active, past_due (card renewal failed, the processor retries), canceled, expired (crypto period ran out). Treat active and past_due as entitled until currentPeriodEnd. The list endpoint filters by status, customerRef and planId.

bash
curl "https://www.getly.store/api/v1/billing/subscriptions/uuid" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": {
    "id": "uuid",
    "customerRef": "user_8841",
    "status": "active",
    "paymentMethod": "stripe",
    "currentPeriodEnd": "2026-10-08T09:14:00.000Z",
    "cancelAtPeriodEnd": false,
    "plan": { "name": "Pro", "amount": 1900, "interval": { "unit": "month", "count": 1 } }
  }
}
POST/api/v1/billing/subscriptions/{id}/cancelwrite:billing

Stop renewal at the end of the paid period - never an immediate cut-off. You receive billing.subscription.canceled when it ends (card) or billing.subscription.expired (crypto). 409 subscription_not_cancellable when already ended.

bash
curl -X POST "https://www.getly.store/api/v1/billing/subscriptions/uuid/cancel" \
  -H "Authorization: Bearer gk_your_api_key"
Response · application/json
{
  "success": true,
  "data": { "id": "uuid", "status": "active", "cancelAtPeriodEnd": true, "currentPeriodEnd": "2026-10-08T09:14:00.000Z" }
}

Getly Billing

Recurring plans for a product you run yourself - a SaaS, a community, a tool. Plans live in the API, the subscribe page lives on getly.store, and your backend grants access from five webhooks that all carry your own customer reference.

Access is by application: tell us the website and what you sell at /dashboard/billing. Until approval, write calls answer 403 billing_not_approved and read calls work.

  • billing.subscription.created
  • billing.subscription.renewed
  • billing.payment_failed
  • billing.subscription.canceled
  • billing.subscription.expired
How Getly Billing works

Deliver your own license keys

A product can hand out the seller's own pre-generated keys - registration keys, voucher codes or unique URLs - instead of one Getly generates. Getly gives out the next key per sale, first pasted first sold, and never blocks a sale on an empty pool: the buyer is told a key is on its way by email, and gets one the instant more are added.

  • Dashboard: product edit → Pricing step → "Deliver my own license keys" (the pool needs a product id, so it is not offered at creation). One key per line, up to 5,000 per add; mutually exclusive with Getly-generated keys on the same product.
  • API: GET/POST /api/v1/products/{id}/keys lists and adds keys; DELETE /api/v1/products/{id}/keys/{keyId} removes an unsold one. keyPoolEnabled and keyPoolLowThreshold live on the product itself, set on POST/PATCH /api/v1/products.

Prefer your own backend? Every sale.completed item now carries licenseKey - the key already issued for that item (from the pool or from Getly's own minting), or null while it is still waiting - alongside buyerEmail, so you can relay it or send your own.

Webhooks

Webhooks allow your application to receive real-time notifications when events happen in your store. Register endpoints at /dashboard/developer/webhooks or via the API.

Event Types

sale.completed - A sale was completed for one of your products. Includes the buyer's email, so you can send your own license key.

product.created - A product was created in your store

product.updated - A product in your store was updated

review.created - A new review was left on one of your products

download.completed - A buyer downloaded one of your products

refund.created - A buyer requested a refund (the decision comes later as order.refunded)

order.refunded - A refund was booked on one of your orders

dispute.created - A buyer opened a dispute

dispute.resolved - A dispute was resolved

checkout_link.completed - A checkout link was paid

license.activated - A license key was activated

access.expiring - A timed-access purchase expires in 7 days

access.expired - A timed-access purchase expired

billing.subscription.created - Getly Billing: a customer subscribed to one of your plans

billing.subscription.renewed - Getly Billing: a subscription renewed and was paid

billing.payment_failed - Getly Billing: a renewal payment failed

billing.subscription.canceled - Getly Billing: a subscription was canceled

billing.subscription.expired - Getly Billing: a crypto-paid subscription expired unpaid

* - Subscribe to all events

Payload Format

Every webhook delivery sends a JSON POST request with the following structure:

POST · application/json
POST https://your-endpoint.com/webhooks
Content-Type: application/json
X-Getly-Signature: sha256=<hmac_hex>
X-Getly-Event: sale.completed

{
  "deliveryId": "uuid",
  "event": "sale.completed",
  "data": {
    "orderId": "uuid",
    "buyerId": "uuid",
    "buyerEmail": "[email protected]",
    "items": [
      {
        "orderItemId": "uuid",
        "productId": "uuid",
        "price": 2999,
        "sellerAmount": 2399,
        "isGift": false,
        "licenseKey": null
      }
    ],
    "total": 2999
  },
  "timestamp": "2026-09-23T10:30:00.000Z"
}

Signature Verification

Every webhook delivery includes an X-Getly-Signature header. Verify the signature using HMAC-SHA256 with your webhook secret to ensure the request came from Getly.

node.js
const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const expected = 'sha256=' +
    crypto
      .createHmac('sha256', secret)
      .update(body)
      .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler
app.post('/webhooks', (req, res) => {
  const signature = req.headers['x-getly-signature'];
  const isValid = verifyWebhook(
    JSON.stringify(req.body),
    signature,
    process.env.GETLY_WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const { event, data } = req.body;
  // Handle the event...

  res.status(200).send('OK');
});

Retry Policy

Failed deliveries (non-2xx response or timeout) are retried automatically with exponential backoff:

Attempt 1: Retry after 1 minute

Attempt 2: Retry after 5 minutes

Attempt 3: Retry after 30 minutes

Attempt 4: Retry after 2 hours

Attempt 5: Give up (delivery marked as failed)

Your endpoint should respond within 10 seconds. Return a 2xx status code to acknowledge receipt.