Not connected

Public API Documentation

Integrate Stone Project with your systems using the Public API. Create tokens in Settings β†’ API Settings to get started.

πŸ”‘ Quick start: Paste your API token above to authenticate and test endpoints directly from this page. Tokens are created in the app at Settings β†’ API Settings.
OpenAPI 3.1: the machine-readable contract is at /docs/openapi.json (import it in Postman, Insomnia or a code generator) and the full reference is at /docs/reference. Connection test: GET /me returns the company and the scopes of the token.

Authentication

All requests must include a valid API token in the Authorization header:

Authorization: Bearer YOUR_API_TOKEN

API tokens are created in the app at Settings β†’ API Settings. Each token has specific scopes that control which resources can be accessed.

Base URL

https://api.stoneproject.app/api/v1/public

All endpoint paths below are relative to this base URL.

Response Format

All responses follow a consistent JSON format:

{
  "success": true,
  "data": { ... },
  "message": "Optional message"
}

Pagination

List endpoints return paginated results. Use per_page (max 100) and page query parameters.

{
  "success": true,
  "data": {
    "items": [ ... ],
    "meta": {
      "current_page": 1,
      "last_page": 3,
      "per_page": 25,
      "total": 60
    }
  }
}

Rate Limiting

The API is rate-limited. The current limits are returned in response headers:

Header Description
X-RateLimit-Limit Maximum requests per window
X-RateLimit-Remaining Requests remaining in current window
Retry-After Seconds until rate limit resets (when exceeded)

Error Handling

Errors return a success: false response with an error code:

{
  "success": false,
  "message": "Token is invalid or revoked",
  "error_code": "token_invalid"
}
HTTP Code Error Code Description
401 token_missing No Authorization header provided
401 token_invalid Token not found or revoked
401 token_revoked Token has been deactivated
401 token_expired Token has expired
403 scope_required Token lacks the required scope
404 β€” Resource not found
422 β€” Validation error (check errors field)
429 β€” Rate limit exceeded

Projects

GET /projects projects.read

List all projects for your company.

β–Ά Try it
Parameter Type Description
status string Filter by status
stage string Filter by kanban stage
client_id uuid Filter by client
assigned_to uuid Filter by assigned team member
search string Search in title and description
created_after date Filter by creation date (after)
created_before date Filter by creation date (before)
with_client boolean Include client data
per_page integer Results per page (max 100)

GET /projects/{id} projects.read

Get a single project by ID. Client data is included automatically.

β–Ά Try it

POST /projects projects.write

Create a new project.

Field Type Description
title string required Project title
description string optional Project description
client_id uuid optional Associated client
stage string optional Kanban stage (default: "new")
priority string optional low, normal, high, urgent
source string optional Lead source
estimated_value numeric optional Estimated value
assigned_to uuid optional Assigned team member
tags array optional List of tags
deadline date optional Project deadline
address string optional Street address
city string optional City
state string optional State / Province
zip_code string optional ZIP / Postal code
contact_name string optional On-site contact name
contact_phone string optional On-site contact phone
address_line_2 string optional Address line 2 (suite, unit, etc.)
country string optional Country
scheduled_measurement_at datetime optional Scheduled measurement date/time (ISO 8601)
scheduled_installation_at datetime optional Scheduled installation date/time (ISO 8601)
purchase_order string optional Customer purchase order reference
curl -X POST https://api.stoneproject.app/api/v1/public/projects \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Kitchen Remodel - Smith",
    "client_id": "019c...",
    "priority": "high",
    "estimated_value": 8500
  }'

PUT /projects/{id} projects.write

Update an existing project. Same fields as POST /projects above, all optional β€” send only what you want to change. Note the method is PUT, not POST: a POST to /projects creates a new project instead of updating one.

The job site street is address. We also accept address_line_1 as an alias for it, since the field beside it is called address_line_2. Any key we do not recognise is ignored and named back to you in the response message, so a typo never passes as a silent success.

curl -X PUT https://api.stoneproject.app/api/v1/public/projects/019c... \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "address": "4501 Cox Rd",
    "address_line_2": "Suite 200",
    "city": "Glen Allen",
    "state": "VA",
    "zip_code": "23060",
    "contact_name": "Faro Vitale",
    "contact_phone": "+1 718 208 3214"
  }'

Response Example

A project object includes:

{
  "success": true,
  "data": {
    "id": "019c...",
    "title": "Kitchen Remodel - Smith",
    "status": "active",
    "stage": "quoted",
    "priority": "high",
    "estimated_value": 8500.00,
    "deadline": "2026-05-15",
    "client": {
      "id": "019c...",
      "name": "John Smith"
    },
    "created_at": "2026-03-10T14:30:00Z",
    "updated_at": "2026-04-01T09:15:00Z"
  }
}

Clients

GET /clients clients.read

List all clients for your company.

β–Ά Try it
Parameter Type Description
search string Search by name, email, phone, or account
source string Filter by lead source
created_after date Filter by creation date (after)
created_before date Filter by creation date (before)
per_page integer Results per page (max 100)

GET /clients/{id} clients.read

Get a single client by ID. Includes project count.

β–Ά Try it

POST /clients clients.write

Create a new client.

Field Type Description
name string required Client name
email string optional Email address
phone string optional Primary phone
cell string optional Cell phone
account_name string optional Company / Account name
address string optional Street address
city string optional City
state string optional State
zip_code string optional ZIP code
notes string optional Internal notes
source string optional Lead source
tags array optional List of tags
external_id string optional Your external reference ID
curl -X POST https://api.stoneproject.app/api/v1/public/clients \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Smith",
    "email": "[email protected]",
    "phone": "555-0100",
    "source": "website"
  }'

PUT /clients/{id} clients.write

Update an existing client. All fields are optional.

Response Example

A client object includes:

{
  "success": true,
  "data": {
    "id": "019c...",
    "name": "John Smith",
    "email": "[email protected]",
    "phone": "555-0100",
    "account_name": "Smith Design Co.",
    "source": "website",
    "projects_count": 3,
    "created_at": "2026-01-20T10:00:00Z"
  }
}

Quotes

GET /quotes quotes.read

List all quotes for your company.

β–Ά Try it
Parameter Type Description
status string Filter by status (draft, sent, accepted, rejected, expired)
project_id uuid Filter by project
client_id uuid Filter by client
created_after date Filter by creation date (after)
created_before date Filter by creation date (before)
with_client boolean Include client data
with_project boolean Include project data
per_page integer Results per page (max 100)

GET /quotes/{id} quotes.read

Get a single quote by ID. Client and project data are included automatically.

β–Ά Try it

Orders

Closed sales orders β€” read-only access to finalized deals. An order is created when a quote is accepted and the sale is closed.

GET /orders orders.read

List all orders for your company.

β–Ά Try it
Parameter Type Description
status string Filter by status (active, cancelled, completed)
payment_status string Filter by payment status (unpaid, partial, paid)
project_id uuid Filter by project
quote_id uuid Filter by the originating quote
sale_date_from date Filter by sale date (on or after, YYYY-MM-DD)
sale_date_to date Filter by sale date (on or before, YYYY-MM-DD)
created_after date Filter by creation date (after)
created_before date Filter by creation date (before)
per_page integer Results per page (max 100)

Response Example

{
  "success": true,
  "data": {
    "items": [
      {
        "id": "019e-...",
        "number": "ORD-2026-00042",
        "name": "Kitchen Renovation β€” Smith",
        "status": "active",
        "payment_status": "partial",
        "currency": "USD",
        "subtotal": 7200.00,
        "tax": 504.00,
        "discount_amount": 0.00,
        "total": 7704.00,
        "amount_paid": 3852.00,
        "balance_due": 3852.00,
        "sale_date": "2026-06-15",
        "payment_due_date": "2026-07-15",
        "payment_terms": "net30",
        "payment_terms_days": 30,
        "deposit_required": true,
        "deposit_percentage": 50,
        "deposit_amount": 3852.00,
        "installments_count": 2,
        "sqft": 48.5,
        "po_number": "PO-98765",
        "salesperson": "Jane Doe",
        "notes": null,
        "project_id": "019c-...",
        "quote_id": "019d-...",
        "lead_name": "John Smith",
        "account_name": "Smith Family",
        "created_at": "2026-06-15T10:00:00Z",
        "updated_at": "2026-06-20T14:00:00Z"
      }
    ],
    "meta": {
      "current_page": 1,
      "last_page": 3,
      "per_page": 10,
      "total": 28
    }
  }
}

GET /orders/{id} orders.read

Get a single order by ID. The line_items array is included only on this detail response (not on the list). Internal cost, commission, and margin keys are stripped from each line item.

β–Ά Try it

Invoices

GET /invoices invoices.read

List all invoices for your company.

β–Ά Try it
Parameter Type Description
status string Filter by status (draft, sent, paid, overdue, cancelled)
project_id uuid Filter by project
client_id uuid Filter by client
created_after date Filter by creation date (after)
created_before date Filter by creation date (before)
due_after date Filter by due date (after)
due_before date Filter by due date (before)
with_client boolean Include client data
with_project boolean Include project data
with_payments boolean Include payments list
per_page integer Results per page (max 100)

GET /invoices/{id} invoices.read

Get a single invoice by ID. Client, project, and payments are included automatically.

β–Ά Try it

Payments

GET /payments payments.read

List all payments for your company.

β–Ά Try it
Parameter Type Description
invoice_id uuid Filter by invoice
status string Filter by status (pending, completed, failed, refunded)
payment_method string Filter by payment method
created_after date Filter by creation date (after)
created_before date Filter by creation date (before)
with_invoice boolean Include invoice data
per_page integer Results per page (max 100)

GET /payments/{id} payments.read

Get a single payment by ID. Invoice summary is included automatically.

β–Ά Try it

Pieces / Inventory

Manage your company's stone pieces and remnants β€” the core of Stone Project.

List Pieces

GET /pieces pieces.read

List your company's pieces with optional filters. Requires the pieces.read scope. To browse public remnants across the whole marketplace (other companies' listings), use the Search endpoint instead.

Parameter Type Description
filter[stone_type_id] uuid Filter by stone type
filter[material_type_id] uuid Filter by material type
filter[category_id] uuid Filter by category
filter[status] string Filter by status: available, reserved, sold
filter[color] string Filter by color name
filter[finish] string Filter by finish: polished, honed, leathered, brushed, flamed
sort string Sort by: price, created_at, views_count, length_cm, width_cm. Prefix with - for descending
latitude number Filter by proximity (requires longitude and radius)
longitude number Filter by proximity
radius number Radius in miles for proximity search
page integer Page number (default: 1, 20 items per page)
β–Ά Try it

Response Example

{
  "data": [
    {
      "id": "019d-...",
      "title": "White Carrara Remnant",
      "description": "Beautiful remnant piece, polished finish",
      "status": "available",
      "dimensions": {
        "length_cm": 120,
        "width_cm": 60,
        "thickness_cm": 3,
        "area_sqft": 7.75
      },
      "price": 350.00,
      "price_type": "fixed",
      "color": "White",
      "finish": "polished",
      "origin": "Italy",
      "stone_type": { "id": "...", "name": "Marble" },
      "category": { "id": "...", "name": "Remnants" },
      "images": [
        { "id": 1, "url": "https://...", "thumbnail_url": "https://..." }
      ],
      "company": { "id": "...", "name": "Stone Works LLC" },
      "views_count": 42,
      "created_at": "2026-03-15T10:30:00Z"
    }
  ],
  "meta": { "current_page": 1, "last_page": 5, "per_page": 20, "total": 98 }
}

Get Piece

GET /pieces/{id} pieces.read

Get a single piece by ID. Includes all details, images, company info, and related stone type/category.

β–Ά Try it

Create Piece

POST /pieces pieces.write

Create a new piece in your inventory. If coordinates are not provided, the company's location is used.

Field Type Required Description
title string Yes Piece title (max 255 chars)
length_cm number Yes Length in centimeters (1–99999)
width_cm number Yes Width in centimeters (1–99999)
thickness_cm number No Thickness in centimeters (0.1–999)
description string No Description (max 5000 chars)
stone_type_id uuid No Stone type ID (marble, granite, etc.)
material_type_id uuid No Material type ID
category_id uuid No Category ID
price number No Price (0–9999999)
price_type string No fixed, negotiable, or on_request
color string No Color name (max 100 chars)
finish string No polished, honed, leathered, brushed, flamed
origin string No Origin / country (max 100 chars)
shape_type string No rectangle, lShape, uShape, triangle, irregular, custom

Update Piece

PUT /pieces/{id} pieces.write

Update an existing piece. All fields are optional β€” only send the fields you want to change.

Delete Piece

DELETE /pieces/{id} pieces.write

Soft-delete a piece. It can be restored later via the app.

Upload Piece Images

POST /pieces/{id}/images pieces.write

Upload images to a piece. Send as multipart/form-data.

Field Type Required Description
images[] file Yes Image files (JPEG, PNG, WebP). Max 10MB each.

Update Piece Status

PATCH /pieces/{id}/status pieces.write

Change the status of a piece.

Field Type Required Description
status string Yes available, reserved, or sold

Suppliers

Manage your stone and material suppliers.

List Suppliers

GET /suppliers suppliers.read

List all suppliers for your company.

Parameter Type Description
filter[name] string Search by supplier name
filter[is_active] boolean Filter active/inactive suppliers
filter[city] string Filter by city
filter[state] string Filter by state
sort string Sort by: name, created_at, city, country
β–Ά Try it

Response Example

{
  "success": true,
  "data": [
    {
      "id": "019d-...",
      "name": "Premium Stone Supply",
      "email": "[email protected]",
      "phone": "+1 555-0123",
      "city": "Miami",
      "state": "FL",
      "country": "US",
      "is_active": true,
      "created_at": "2026-01-10T08:00:00Z"
    }
  ]
}

Get Supplier

GET /suppliers/{id} suppliers.read

Get a single supplier by ID.

β–Ά Try it

Create Supplier

POST /suppliers suppliers.write

Create a new supplier.

Field Type Required Description
name string Yes Supplier name (max 255)
email string No Contact email
phone string No Phone number (max 20)
address string No Street address
city string No City
state string No State / Province
country string No Country
postal_code string No Postal code (max 20)
website string No Website URL
tax_id string No Tax ID / EIN (max 30)
notes string No Internal notes (max 2000)

Update Supplier

PUT /suppliers/{id} suppliers.write

Update an existing supplier. All fields are optional.

Delete Supplier

DELETE /suppliers/{id} suppliers.write

Delete a supplier.

Tasks

GET /tasks tasks.read

List tasks, newest first. Filters: project_id, status (pending, in_progress, completed, dismissed, snoozed).

POST /tasks tasks.write

Create a task. title is required; optional description, project_id (a project of this company), assigned_to (an active member; defaults to the owner), priority (low, medium, high, urgent) and due_date (defaults to today).

Leads

POST /leads leads.write

Create a lead: the client (matched by email, then phone, so the same person stays one client) and a project in the lead stage, exactly like a website form does. name and email or phone are required; optional message, address fields and utm_source/utm_medium/utm_campaign. The same lead sent twice within 10 minutes returns the first one with deduplicated: true.

Time entries

GET /time-entries time_entries.read

Clock punches, newest first: who, check-in and check-out, break and total minutes, review status. Filters: user_id, from, to (dates). GPS and photos are never exposed.

Webhook subscriptions

REST hooks for Zapier, Make and your own code: subscribe a URL when your automation turns on, unsubscribe when it turns off. Deliveries are the same signed, retried webhooks described below.

GET /webhooks/events webhooks.manage
GET /webhooks webhooks.manage
POST /webhooks webhooks.manage

Body: url (public https), events (array of event names, or ["*"]), optional name. The response carries the signing secret once.

DELETE /webhooks/{id} webhooks.manage

Search the Remnant Finder marketplace for pieces across all companies.

Search Pieces

GET /search/pieces

Full-text search across public pieces. Supports filters, geo search, and sorting.

Parameter Type Description
q string Free text search query
stone_type_id uuid Filter by stone type
material_type_id uuid Filter by material type
category_id uuid Filter by category
color string Filter by color name
finish string Filter by finish type
min_price number Minimum price filter
max_price number Maximum price filter
min_length number Minimum length in cm
min_width number Minimum width in cm
latitude number Center point latitude for geo search
longitude number Center point longitude for geo search
radius number Search radius in miles
sort string price, -price, created_at, -created_at, distance
β–Ά Try it

Stone Types

Reference data for stone types (Marble, Granite, Quartz, etc.). Read-only.

List Stone Types

GET /stone-types

Returns all available stone types. Requires a valid API token (Bearer); no specific scope needed. Each item also includes slug and icon.

β–Ά Try it

Response Example

{
  "data": [
    { "id": "019a-...", "name": "Marble" },
    { "id": "019a-...", "name": "Granite" },
    { "id": "019a-...", "name": "Quartz" },
    { "id": "019a-...", "name": "Quartzite" },
    { "id": "019a-...", "name": "Porcelain" }
  ]
}

Categories

Piece categories (Slabs, Remnants, Countertops, etc.). Read-only.

List Categories

GET /categories

Returns all active categories, ordered by display order. Requires a valid API token (Bearer); no specific scope needed. Each item includes id, name, slug, icon.

β–Ά Try it

Colors

System and custom colors used for pieces.

List Colors

GET /colors

Returns system colors plus your company's custom colors.

Parameter Type Description
search string Search by color name
β–Ά Try it

Create Color

POST /colors

Create a custom color for your company.

Field Type Required Description
name string Yes Color name (max 100)
hex_code string No Hex code (e.g. #FFFFFF)

Origins

Stone origin countries and regions.

List Origins

GET /origins

Returns system origins plus your company's custom origins.

Parameter Type Description
search string Search by origin name
β–Ά Try it

Create Origin

POST /origins

Create a custom origin for your company.

Field Type Required Description
name string Yes Origin name (max 100)
country_code string No ISO 2-letter country code
flag_emoji string No Flag emoji (max 10)

Available Scopes

Each API token is created with specific scopes that control access:

Scope Description
projects.read View projects
projects.write Create and update projects
clients.read View clients
clients.write Create and update clients
quotes.read View quotes
invoices.read View invoices
payments.read View payments
orders.read View orders
pieces.read View pieces and inventory
pieces.write Create, update, and delete pieces
suppliers.read View suppliers
suppliers.write Create, update, and delete suppliers
colors.write Create custom colors
origins.write Create custom origins

Read-only reference data (stone types, categories, colors, origins) and the marketplace Search require only a valid token β€” no specific scope.

Webhooks

Webhooks allow you to receive real-time notifications when events occur in your account. Configure webhook URLs in Settings β†’ API Settings β†’ Webhooks.

Subscribe one or more HTTPS endpoints to the events your integration cares about. Each delivery is signed (HMAC-SHA256) so you can verify it came from us β€” see Verifying the signature below.

Available Events

Event Description
quote.created A new quote was created
quote.sent A quote was sent to the client
quote.accepted A quote was accepted
quote.rejected A quote was rejected
quote.expired A quote expired
quote.deposit_paid A quote deposit was paid
project.created A new project was created. Besides data.client_id, the payload embeds the whole client under data.client (name, account name, email, phone, city/state/country, source) so you can create the contact in your CRM without a second call. It is null only when the project has no client attached.
project.stage_changed A project moved to a new stage (the payload's previous holds the prior stage). Use the data.stage value to react to production, installation, completion, etc.
client.created A new client was added
client.updated A client's public details changed
invoice.paid An invoice was marked as paid
payment.received A payment was recorded
order.created A new order was created (sale closed)
order.status_changed An order changed status. The payload includes "previous": { "status": "active" } with the prior value.
time_entry.clocked_in A crew member clocked in
time_entry.clocked_out A crew member clocked out (the payload carries the total minutes)

Subscribe to * to receive every event.

Webhook Payload

Every delivery is a JSON POST with a stable envelope. data is the public representation of the entity (the same shape as the matching GET endpoint). previous is present only on change events and holds the prior values of the changed fields.

{
  "id": "evt_01J9Z4Q1ABC...",
  "event": "quote.accepted",
  "api_version": "v1",
  "created_at": "2026-06-26T14:30:00+00:00",
  "company_id": "019c...",
  "data": {
    "id": "019d...",
    "number": "Q-2026-00012",
    "project_id": "019c...",
    "status": "accepted",
    "total": 8500.00,
    "accepted_at": "2026-06-26T14:30:00+00:00"
  },
  "previous": { "status": "sent" }
}

The id (evt_…) is unique per event β€” use it to make your handler idempotent, since a delivery may be retried. Delivery order is best-effort, not guaranteed.

Verifying the signature

Each request carries these headers:

Header Value
X-RemnantFinder-Signature t=<unix_timestamp>,v1=<hex_hmac>
X-RemnantFinder-Event The event name (e.g. quote.accepted)
X-RemnantFinder-Delivery Unique id of this delivery attempt

The signature is HMAC-SHA256 of the string "{t}.{raw_request_body}", keyed with your endpoint's signing secret (shown only once, when you create or rotate the endpoint). Verify it against the raw request body (before any JSON parsing), and reject timestamps outside a tolerance window (e.g. 5 minutes) to prevent replay attacks.

$payload = file_get_contents('php://input');
$header  = $_SERVER['HTTP_X_REMNANTFINDER_SIGNATURE']; // "t=...,v1=..."
parse_str(strtr($header, ',', '&'), $p);

$expected = hash_hmac('sha256', $p['t'] . '.' . $payload, $YOUR_SIGNING_SECRET);

if (! hash_equals($expected, $p['v1']) || abs(time() - (int) $p['t']) > 300) {
    http_response_code(400);
    exit; // reject: bad signature or stale timestamp
}
// trusted β€” respond 2xx quickly to acknowledge receipt

Delivery & retries

Need help? Contact us at [email protected]