Developers

Seyls API reference

Connect your webstore, accounting tool, or anything else to your outlet. Read and update customers, products, and sales over plain JSON.

Version 1 · Base URL https://seyls.com/api/v1

Overview

The Seyls API gives every outlet its own keys, its own data, and nothing else. A key created for an outlet can only ever see and change that outlet.

All requests use the base URL below, send and receive JSON, and require HTTPS.

https://seyls.com/api/v1

Send an Accept: application/json header with every request. A quick first call:

curl https://seyls.com/api/v1/customers \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

Authentication

Create keys in the app under Settings > API Keys. Each key belongs to the outlet it was created in and carries the permissions you picked:

PermissionAllows
customers:readList and read customers
customers:writeCreate, update, and archive customers
products:readList and read products
products:writeCreate, update, and archive products
sales:readList and read sales

Pass the key as a bearer token on every request:

Authorization: Bearer YOUR_API_KEY

We show the key in full exactly once, right after you create it. Store it in your server's secret store and never ship it in a browser or mobile app. Revoking a key in Settings stops it working immediately, and keys can be created with an expiry date.

Rate limits

Each outlet can make 60 requests per minute, counted across all of its keys. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining headers. Past the limit we answer 429 with a Retry-After header telling you how many seconds to wait.

Errors

Errors are JSON with a human readable message:

{"message": "Resource not found."}

Validation failures list the problems per field:

{
  "message": "The name field is required.",
  "errors": {"name": ["The name field is required."]}
}
StatusMeaning
401Missing, revoked, or expired key, or a key of the wrong type
403The key does not have the required permission
404No such record in this outlet
422Validation failed, see the errors object
429Rate limit reached, retry after the given delay
500Something failed on our side

Pagination

List endpoints are paginated. Pass page and per_page (default 25, maximum 100). Responses wrap records in data with links and meta for paging:

{
  "data": [ ... ],
  "links": {"first": "...", "last": "...", "prev": null, "next": "..."},
  "meta": {"current_page": 1, "last_page": 4, "per_page": 25, "total": 96}
}

Identifiers

Every record has an id that is an opaque string, for example "3RmKq8ZlYVxE". Ids are stable for the life of the record and are the only way to address it. They are not numbers and carry no meaning you can parse, so store them as strings.

Customers

A customer object:

{
  "id": "3RmKq8ZlYVxE",
  "name": "Aminath Waheeda",
  "email": "aminath@example.com",
  "phone": "+960 7771234",
  "address": "M. Blue House, Male'",
  "tin": null,
  "notes": null,
  "credit_limit": 5000.0,
  "status": "active",
  "created_at": "2026-05-02T09:14:07+05:00",
  "updated_at": "2026-08-01T16:40:22+05:00"
}
GET/customers

Lists customers, newest first. Needs customers:read.

ParameterDescription
search optionalMatches name, phone, email, or TIN
status optionalactive (default) or archived
page, per_page optionalSee pagination
GET/customers/{id}

Returns one customer. Needs customers:read.

POST/customers

Creates a customer and returns it with status 201. Needs customers:write.

FieldDescription
name requiredCustomer name
email optionalA valid email address
phone optionalContact number
address optionalAddress line
tin optionalMaldives TIN in the standard format
notes optionalFree text notes
credit_limit optionalNumber, defaults to 0
curl -X POST https://seyls.com/api/v1/customers \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"name": "Aminath Waheeda", "phone": "+960 7771234"}'
PATCH/customers/{id}

Updates the fields you send and leaves the rest untouched. Same fields as create. Needs customers:write.

DELETE/customers/{id}

Archives the customer and answers 204. Sales history stays intact, and the customer can be restored inside the app. Needs customers:write.

Products

A product object:

{
  "id": "Xw2PqL9dKm4T",
  "code": "SKU-0042",
  "name": "Coconut Water 500ml",
  "description": null,
  "barcode": "8964000551234",
  "unit": "PCS",
  "cost_price": 8.5,
  "selling_price": 12.0,
  "tax": {"name": "GST", "rate": 8.0},
  "stock": {"tracked": true, "quantity": 240.0, "reorder_level": 24.0, "reorder_quantity": 48.0},
  "images": [
    {
      "url": "https://cdn.seyls.com/product_pictures/8f2k.jpg",
      "thumbnail": "https://cdn.seyls.com/product_pictures/thumb_8f2k.jpg"
    }
  ],
  "units": [{"unit": "PCS", "price": 12.0, "quantity": 1.0}],
  "status": "active",
  "created_at": "2026-03-11T10:02:44+05:00",
  "updated_at": "2026-08-09T18:21:03+05:00"
}

Prices exclude tax. Stock quantities are read only through the API: they change through sales, goods received, and stock adjustments inside Seyls, so every movement stays accounted for. Product photos are uploaded inside Seyls; the API lists their url and thumbnail links, ready to show on a webstore.

GET/products

Lists products, newest first. Needs products:read.

ParameterDescription
search optionalMatches code, name, or barcode
status optionalactive (default) or archived
page, per_page optionalSee pagination
GET/products/{id}

Returns one product. Needs products:read.

POST/products

Creates a product and returns it with status 201. Needs products:write.

FieldDescription
code requiredProduct code, unique within your business
name requiredProduct name
unit requiredUnit of measure, for example PCS or KG
cost_price requiredCost per unit, up to 4 decimals
selling_price requiredSelling price per unit, excluding tax
tax_rate requiredMust match one of the outlet's active tax rates, use 0 for no tax if a zero rate is configured
barcode optionalBarcode
reorder_level optionalStock level that flags a reorder
reorder_quantity optionalQuantity to reorder
PATCH/products/{id}

Updates the fields you send. Available: name, barcode, reorder_level, reorder_quantity, tax_rate, and track_stock (boolean). Prices, code, and units are managed inside Seyls where price change history is recorded. Needs products:write.

DELETE/products/{id}

Archives the product and answers 204. It disappears from sale screens but keeps its history, and can be restored inside the app. Needs products:write.

Sales

Sales are read only through the API. A sale in Seyls is rung up at the register by a signed in user with an open cash drawer, so creating one belongs to the POS. The API is how the rest of your tools see them.

A sale object:

{
  "id": "kX9mP2wQ4z",
  "number": "INV-2026-01184",
  "type": "credit",
  "date": "09/08/2026",
  "due_date": "2026-09-08",
  "reference": "PO-556",
  "customer": {"id": "3RmKq8ZlYVxE", "name": "Aminath Waheeda"},
  "currency": "MVR",
  "exchange_rate": 1.0,
  "amounts": {"subtotal": 1150.0, "tax": 92.0, "discount": 0.0, "rounding": 0.0, "total": 1242.0},
  "paid": false,
  "balance_due": 742.0,
  "items": [
    {
      "product": {"id": "Xw2PqL9dKm4T", "code": "SKU-0042", "name": "Coconut Water 500ml"},
      "quantity": 10.0,
      "unit": "PCS",
      "unit_price": 12.0,
      "discount": 0.0,
      "tax_rate": 8.0,
      "tax": 9.6,
      "line_total": 120.0
    }
  ],
  "created_at": "2026-08-09T14:03:51+05:00",
  "updated_at": "2026-08-09T14:03:51+05:00"
}

Amounts are in the outlet's base currency. For sales made in another currency, currency and exchange_rate describe the sale currency and the rate frozen at the time of sale. type is one of cash, credit, hold, refunded, or quote. paid is true for cash sales, reflects the credit balance for credit sales, and is null for other document types. The date field follows the outlet's configured date format; use created_at when you need a machine readable timestamp.

GET/sales

Lists sales, newest first. Without a type filter we return completed documents: cash, credit, and refunded. Line items are only included on the single sale endpoint. Needs sales:read.

ParameterDescription
type optionalcash, credit, hold, refunded, or quote
from optionalInclude sales created on or after this date, format YYYY-MM-DD
to optionalInclude sales created on or before this date, format YYYY-MM-DD
customer optionalA customer id, returns that customer's sales
page, per_page optionalSee pagination
GET/sales/{id}

Returns one sale with its line items. Needs sales:read.

GET/sales/{id}/pdf

Downloads the sale's invoice as a PDF, rendered with the same template, logo, and numbering your printed invoices use. The response is the PDF itself with Content-Type: application/pdf. Held bills answer 422 because they have no invoice document yet. Needs sales:read.

curl -o invoice.pdf https://seyls.com/api/v1/sales/kX9mP2wQ4z/pdf \
  -H "Authorization: Bearer YOUR_API_KEY"

Need something the API does not cover?

Tell us what you are building through the contact page. We grow the API from real integrations, the same way we grow Seyls itself.