Servicejoy REST API

Integrate your systems with Servicejoy to create clients, submit orders, generate invoices, and pull payment records — all programmatically.

🔒 OAuth 2.0 📋 JSON 🔄 REST ⚡ client_credentials flow

Overview

All API endpoints are served from your Servicejoy account domain. Replace {your-subdomain} with the subdomain provided in your API settings.

BASE URL
https://{your-subdomain}.servicejoy.com

Request Format

All POST request bodies must be JSON with the header Content-Type: application/json, except the token endpoint which uses form-encoded data.

Response Format

All responses are JSON. Successful responses return HTTP 200 with the result object. Errors return a non-2xx status with a JSON error body.

Authentication

The API uses the OAuth 2.0 client_credentials flow. Obtain your client_id and client_secret from Settings → API inside your Servicejoy account.

POST /api/token Obtain access token
🔓 No authentication required

Exchange your credentials for a JWT access token. Tokens expire after 30 minutes. Re-authenticate before making subsequent calls.

Request

Content-Type: application/x-www-form-urlencoded

FieldTypeRequiredDescription
grant_typestringrequiredMust be client_credentials
client_idstringrequiredYour API client ID (your account subdomain)
client_secretstringrequiredYour API secret key
cURL
Python
JavaScript
REQUEST
curl -X POST https://{your-subdomain}.servicejoy.com/api/token \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_SECRET"
REQUEST
import requests

response = requests.post(
    "https://{your-subdomain}.servicejoy.com/api/token",
    data={
        "grant_type": "client_credentials",
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_SECRET"
    }
)
token = response.json()["access_token"]
REQUEST
const res = await fetch("https://{your-subdomain}.servicejoy.com/api/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_SECRET"
});
const { access_token } = await res.json();

Response

200 OK
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type":    "Bearer",
  "expires_in":    1800
}
Using the token: Include the token in every subsequent request as Authorization: Bearer <access_token>

Error Handling

StatusMeaningCommon Cause
400Bad RequestMissing required field or invalid value
401UnauthorizedMissing or expired access token
403ForbiddenToken does not have access to this resource
404Not FoundResource does not exist
500Server ErrorUnexpected server-side error

Error responses include a JSON body:

ERROR RESPONSE
{
  "error":   "Unauthorized",
  "message": "Invalid or expired access token"
}

Clients

Manage your client organizations. Each client may have multiple contacts.

GET /api/Clients List all active clients
🔑 Bearer token required

Returns all active clients for your account, ordered by name.

REQUEST
curl https://{your-subdomain}.servicejoy.com/api/Clients \
  -H "Authorization: Bearer ACCESS_TOKEN"

Response — array of Client objects

200 OK
[
  {
    "client_org_id": 42,
    "name":          "Acme Corp",
    "currency":      "USD",
    "phone":         "555-123-4567",
    "fax":           null,
    "country":       "United States",
    "street1":       "123 Main St",
    "street2":       null,
    "city":          "Springfield",
    "stateProvince": "IL",
    "postalCode":    "62701",
    "notes":         null,
    "state":         1,
    "contacts": [
      {
        "contact_id":    101,
        "client_org_id": 42,
        "first_name":    "Jane",
        "last_name":     "Doe",
        "email":         "jane.doe@acme.com",
        "home_phone":    null,
        "mobile_phone":  "555-987-6543",
        "enable_login":  true,
        "login":         "jane.doe@acme.com",
        "invited":       true
      }
    ]
  }
]
GET /api/Clients/{id} Get client by ID
🔑 Bearer token required
ParameterTypeDescription
idinteger (path)The client_org_id
REQUEST
curl https://{your-subdomain}.servicejoy.com/api/Clients/42 \
  -H "Authorization: Bearer ACCESS_TOKEN"

Returns a single Client object (same schema as list) or 404 if not found.

POST /api/Clients Create or update a client
🔑 Bearer token required

Pass client_org_id: 0 (or omit it) to create a new client. Pass an existing ID to update.

Request Body

FieldTypeRequiredDescription
client_org_idintegeroptionalOmit or set to 0 for new client
namestringrequiredCompany / client name
currencystringoptionalISO currency code, e.g. "USD"
phonestringoptional
faxstringoptional
countrystringoptionalCountry name, 2-letter, or 3-letter code
street1stringoptional
street2stringoptional
citystringoptional
stateProvincestringoptional
postalCodestringoptional
notesstringoptional
contactsarrayoptionalArray of contact objects (see below)

Contact Object

FieldTypeDescription
contact_idintegerOmit or 0 to add new contact
first_namestring
last_namestring
emailstringUsed for portal login & email notifications
mobile_phonestring
enable_loginbooleanAllow contact to log into the client portal
REQUEST
curl -X POST https://{your-subdomain}.servicejoy.com/api/Clients \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name":     "Acme Corp",
    "currency": "USD",
    "phone":    "555-123-4567",
    "country":  "US",
    "street1":  "123 Main St",
    "city":     "Springfield",
    "stateProvince": "IL",
    "postalCode": "62701",
    "contacts": [
      {
        "first_name": "Jane",
        "last_name":  "Doe",
        "email":      "jane.doe@acme.com",
        "enable_login": true
      }
    ]
  }'

Returns the saved Client object.

Invoices

GET /api/Invoices List invoices with optional filters
🔑 Bearer token required

Query Parameters

ParameterTypeDescription
startstringStart date filter, e.g. 1/1/2024 or 2024-01-01
endstringEnd date filter
invoice_numberstringPartial match on invoice number
client_namestringPartial match on client name (case-insensitive)
REQUEST
curl "https://{your-subdomain}.servicejoy.com/api/Invoices?start=1/1/2024&end=12/31/2024" \
  -H "Authorization: Bearer ACCESS_TOKEN"

Response — array of Invoice objects

200 OK
[
  {
    "invoice_id":     1001,
    "client_name":    "Acme Corp",
    "currency":       "USD",
    "order_id":       55,
    "order_number":   "ORD-000055",
    "invoice_number": "INV-2024-001",
    "invoice_date":   "1/15/2024",
    "invoice_due":    "2/15/2024",
    "total":          1500.00,
    "outstanding":    1500.00,
    "status":         "Sent",
    "invoiceLines": [
      {
        "item_title": "Web Design",
        "descr":      "Homepage redesign",
        "cost":       1500.00,
        "qty":        1,
        "tax1_rate":  null,
        "is_header":  false
      }
    ]
  }
]
POST /api/Invoices Create an invoice
🔑 Bearer token required

Creates an invoice for an existing client (matched by name). If no client with that name exists, a new one is created. An email notification with a payment link is sent to the client's primary contact.

Request Body

FieldTypeRequiredDescription
client_namestringrequiredClient name (case-insensitive match or new)
currencystringoptionalISO code, defaults to USD
invoice_numberstringoptionalIf omitted, the next number in your account's invoice numbering sequence (the same numbering the app uses)
invoice_datestringoptionalDate string, defaults to today
invoice_duestringoptionalDue date, defaults to 30 days from today
po_numberstringoptionalPurchase order reference
termsstringoptional
notesstringoptional
discountdecimaloptionalDiscount amount or percentage
discount_parintegeroptional1 = percentage, 0 = fixed amount
stripebooleanoptionalEnable Stripe payment button
paypalbooleanoptionalEnable PayPal payment button
code_fieldstringoptionalCustom reference / tracking code
gclidstringoptionalGoogle Click ID for ad tracking
invoiceLinesarrayrequiredArray of line item objects (see below)

Invoice Line Object

FieldTypeDescription
item_titlestringLine item title
descrstringDescription
costdecimalUnit price
qtydecimalQuantity
tax1_ratedecimalFirst tax rate (%)
tax1_namestringFirst tax label, e.g. "GST"
tax2_ratedecimalSecond tax rate (%)
tax2_namestringSecond tax label
is_headerbooleanIf true, this is a section header (no price)
REQUEST
curl -X POST https://{your-subdomain}.servicejoy.com/api/Invoices \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client_name":  "Acme Corp",
    "currency":     "USD",
    "invoice_date": "1/15/2024",
    "invoice_due":  "2/15/2024",
    "terms":        "Net 30",
    "stripe":       true,
    "invoiceLines": [
      {
        "item_title": "Web Design",
        "descr":      "Homepage redesign",
        "cost":       1500.00,
        "qty":        1
      },
      {
        "item_title": "SEO Setup",
        "cost":       500.00,
        "qty":        1,
        "tax1_rate":  8.5,
        "tax1_name":  "Tax"
      }
    ]
  }'
POST /api/Invoices/SubmitOrder Create order + invoice, returns payment link
🔑 Bearer token required

Creates a project (order) and an attached invoice in a single call. Returns the full input object with payment_link populated — a direct URL your customer can use to view and pay the invoice.

See the Submission / Submit Order section for the full request schema including client and order objects.
POST /api/Invoices/ChangeInvoiceState Soft-delete or archive an invoice
🔑 Bearer token required
FieldTypeDescription
invoice_numberstringInvoice number to target
stateinteger0 = Deleted, 1 = Active, 2 = Archived
REQUEST
curl -X POST https://{your-subdomain}.servicejoy.com/api/Invoices/ChangeInvoiceState \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "invoice_number": "API-000001", "state": 2 }'

Returns 200 OK on success.

Payments

GET /api/Payments List payments with optional filters
🔑 Bearer token required

Query Parameters

ParameterTypeDescription
startstringPayment date from
endstringPayment date to
invoice_numberstringPartial match on invoice number
client_namestringPartial match on client name
REQUEST
curl "https://{your-subdomain}.servicejoy.com/api/Payments?start=1/1/2024" \
  -H "Authorization: Bearer ACCESS_TOKEN"

Response — array of Payment objects

200 OK
[
  {
    "paymentId":           501,
    "invoice":              "INV-2024-001",
    "invoiceTotal":         1500.00,
    "invoiceStatus":        "Paid",
    "codeField":            null,
    "client":               "Acme Corp",
    "email":                "jane.doe@acme.com",
    "project":              null,
    "agent":                null,
    "createdBy":            null,
    "paymentMethod":        "Credit Card",
    "notes":                null,
    "date":                 "2/1/2024",
    "amount":               1500.00,
    "currency":             "USD",
    "paymentGateway":       "Stripe",
    "transactionId":        "ch_3OxABC1234",
    "transactionDateUTC":   "2024-02-01T14:22:11.0000000Z",
    "onlinePaymentIPAddress": "192.0.2.1",
    "terms":                "Net 30"
  }
]
GET /api/Payments/from/{id} Get all payments with ID greater than {id}
🔑 Bearer token required

Useful for incremental sync. Store the last PaymentId you processed and pass it here to retrieve only new payments.

REQUEST
curl https://{your-subdomain}.servicejoy.com/api/Payments/from/500 \
  -H "Authorization: Bearer ACCESS_TOKEN"

Returns payments ordered by PaymentId ascending (same schema as list).

Submission

High-level endpoints that create client, order, and invoice in a single call. These are the most common integration points. All return a payment_link — a unique URL the client can open to view and pay their invoice.

POST /api/Submission/SubmitOrder Create client + order + invoice in one call
🔑 Bearer token required

The most complete submission endpoint. Pass a client, order, and invoice together. The client is matched by name (or created). The system emails the client a payment link and returns it in the response.

Request Body

FieldTypeDescription
clientobjectClient object (see Clients section)
orderobjectOrder object (see below). Can be omitted.
invoiceobjectInvoice object (see Invoices section)

Order Object

FieldTypeDescription
titlestringOrder / project title
descrstringDescription
statusintegerStatus code (0 = new)
deadlinestring (ISO date)Optional deadline
client_visiblebooleanShow in client portal
REQUEST
curl -X POST https://{your-subdomain}.servicejoy.com/api/Submission/SubmitOrder \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {
      "name":     "Acme Corp",
      "phone":    "555-123-4567",
      "country":  "US",
      "contacts": [
        {
          "first_name": "Jane",
          "last_name":  "Doe",
          "email":      "jane.doe@acme.com",
          "enable_login": true
        }
      ]
    },
    "order": {
      "title":          "Website Redesign Project",
      "descr":          "Full site overhaul",
      "status":         0,
      "client_visible": true
    },
    "invoice": {
      "currency":     "USD",
      "invoice_date": "1/15/2024",
      "invoice_due":  "2/15/2024",
      "stripe":       true,
      "invoiceLines": [
        {
          "item_title": "Web Design",
          "cost":       2000.00,
          "qty":        1
        },
        {
          "item_title": "Hosting Setup",
          "cost":       150.00,
          "qty":        1
        }
      ]
    }
  }'

Response

200 OK
{
  "client": { /* saved client object */ },
  "order": {
    "order_id":     55,
    "order_number": "ORD-000055",
    "title":        "Website Redesign Project"
  },
  "invoice": {
    "invoice_id":     1001,
    "invoice_number": "API-001001",
    "total":          2150.00,
    "status":         "Sent"
  },
  "payment_link": "https://acme.servicejoy.com/External/ExtAccess/view/a3f9e2b1..."
}
POST /api/Submission/SubmitInvoice Create client + invoice (no order)
🔑 Bearer token required

Same as SubmitOrder but without creating a project/order. Use this when you just need an invoice.

REQUEST
curl -X POST https://{your-subdomain}.servicejoy.com/api/Submission/SubmitInvoice \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client": {
      "name": "Acme Corp",
      "contacts": [{ "first_name": "Jane", "email": "jane@acme.com" }]
    },
    "invoice": {
      "currency": "USD",
      "stripe":   true,
      "invoiceLines": [
        { "item_title": "Consulting", "cost": 250.00, "qty": 4 }
      ]
    }
  }'

Returns same structure as SubmitOrder (with payment_link, without order).

Token Management

DELETE /api/RefreshTokens?id={token_id} Revoke a refresh token
🔑 Bearer token required
ParameterTypeDescription
idstring (query)The refresh token ID to revoke
REQUEST
curl -X DELETE "https://{your-subdomain}.servicejoy.com/api/RefreshTokens?id=TOKEN_ID" \
  -H "Authorization: Bearer ACCESS_TOKEN"

Returns 204 No Content on success, 404 if token not found.

Complete Integration Example

The following Python example shows a full end-to-end flow: authenticate, submit an order, and retrieve the payment link.

PYTHON — end-to-end example
import requests

BASE_URL = "https://acme.servicejoy.com"

# 1. Authenticate
token_res = requests.post(f"{BASE_URL}/api/token", data={
    "grant_type":   "client_credentials",
    "client_id":    "acme",
    "client_secret": "YOUR_SECRET_HERE"
})
token_res.raise_for_status()
access_token = token_res.json()["access_token"]

headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type":  "application/json"
}

# 2. Submit an order with invoice
order_res = requests.post(f"{BASE_URL}/api/Submission/SubmitOrder",
    headers=headers,
    json={
        "client": {
            "name": "ACME Corp",
            "contacts": [{
                "first_name": "Jane",
                "last_name":  "Doe",
                "email":      "jane@acme.com",
                "enable_login": True
            }]
        },
        "order": {
            "title":  "Q1 Service Contract",
            "status": 0
        },
        "invoice": {
            "currency": "USD",
            "stripe":   True,
            "invoiceLines": [
                {"item_title": "Monthly Retainer", "cost": 1200.00, "qty": 1},
                {"item_title": "Extras",           "cost":  350.00, "qty": 1}
            ]
        }
    }
)
order_res.raise_for_status()
result = order_res.json()

print("Invoice:",      result["invoice"]["invoice_number"])
print("Total:",        result["invoice"]["total"])
print("Payment link:", result["payment_link"])

# 3. Poll for new payments (incremental sync)
last_payment_id = 0  # store and persist this between runs

payments_res = requests.get(
    f"{BASE_URL}/api/Payments/from/{last_payment_id}",
    headers=headers
)
payments = payments_res.json()
for p in payments:
    print(f"Payment {p['paymentId']}: {p['client']} paid {p['amount']} {p['currency']}")
    last_payment_id = p["paymentId"]  # save this for next run