# Prelien Pro API > Developer-first infrastructure for construction payment compliance. Create and > manage divisions, projects, parties, notices, waivers, escalations and pay > applications; generate the PDFs; and — the part software alone can't do — mail > the physical parcels (accountable USPS) or hand the research to a skilled human > via a fulfillment request. Built on Laravel, REST, JSON, versioned at > `/api/v1/`. This file orients an AI agent or its developer. It is deliberately a curated overview, not the full endpoint inventory. - Machine-readable spec (always public): `{BASE_URL}/api/openapi.json` (or `.yaml`) - Discovery document (always public): `{BASE_URL}/api` - Error catalog: `{BASE_URL}/llms/error-catalog.md` - Human-browsable docs + Postman: `{BASE_URL}/docs`, `/docs.postman` (may be disabled in production; the OpenAPI JSON above is the stable entry point) Replace `{BASE_URL}` below with the deployment host (e.g. `https://api.prelien.pro`). ## Authentication - Bearer token (Laravel Sanctum personal access token): `Authorization: Bearer ` - Mint tokens from the dashboard (key management). A token's **abilities** configure its behaviour: - `mode:sandbox` — sandbox key: isolated, disposable data; no credit spend; no real mail or CLS submissions. Absence of this ability = **live**. - `tier:starter` | `tier:growth` | `tier:scale` — rate limit + claimant cap. Missing = `starter`. - `org:` — pins the billing/acting organization for the key. - The **billing organization** for a request is resolved as: `org:` ability → `X-Organization-UUID` header → the user's first organization. It must be an organization the token's user belongs to. - `organization_id` in a **request body** identifies a target resource or counterparty — it is not the billing selector. ## Sandbox (use this for all development) - Use a `mode:sandbox` token. `GET {BASE_URL}/api/v1/sandbox` reports the current mode. `POST {BASE_URL}/api/v1/sandbox/reset` wipes disposable sandbox data. - Sandbox requests never debit credits and never cause real-world mail or CLS side effects. ## Conventions - All public IDs are UUIDs. All responses are JSON (Eloquent API Resources). - **Idempotency**: send `Idempotency-Key: ` on state-changing/billable POSTs so an immediate retry is not billed twice (default 60s window). Fulfillment requests additionally require an `idempotency_key` body field. - **Rate limits**: per tier per minute — starter 60, growth 300, scale 1000. `429` with `Retry-After` on exceed. - **Pagination**: list endpoints take `per_page` (default 15, max 100, clamped). Search takes `per_type` (default 5, max 25). - **Free-form bags** (`metadata`, `meta`, `payload`) are bounded: ≤ 32 KB, ≤ depth 6, ≤ 500 items. - **Errors**: JSON. `401` unauthenticated, `403` not authorized for that org, `404` unknown UUID, `422` validation + domain preflight, `402` insufficient credits, `429` rate limited. See `/llms/error-catalog.md`. ## Credits (live mode only) - **1 credit = $1.** Plans grant credits at face value. Costs are account-configurable; defaults: document generation 1.00, per-request API call 0.10, CLS fulfillment by `fulfillment_scope` — research 20, research_and_document 30, full_service 35 **+ postage** — certified mail 12 per recipient (standard mail 2.50 where the state allows it, e.g. AZ), mail only (a document order) 5 + postage. - Read-only document endpoints, `support/*`, `credits/*` and `billing/*` are free. - **CLS-billable** events (`mail_sending`, `notice_research`, `document_order`, `fulfillment_notice_research`) require **CLS-eligible** credits — from a paid plan or an ad-hoc purchase. Free / starter-plan credits can never pay for them. - `GET {BASE_URL}/api/v1/credits/balance` — balance breakdown. - `POST {BASE_URL}/api/v1/credits/price` — preview the cost of an action before committing. - Short balance → `402` with `{ message, balance, required, shortfall, cls_billable, quote?, purchase }` — `purchase.credits` is what to buy (`POST /api/v1/credits/purchase`) before retrying. - **Held credits** — some charges are not final when you ask for them (full-service postage). Those credits are *held*: debited from your spendable balance but not yet spent, then charged for what was actually used with the rest returned. `credits/balance` returns `held` and `holds[]`, each with `for: { type, id }` naming the request it belongs to. ## Core model and the golden path Records are **not** duplicated per organization. One pay application exists once; organizations attach to it through participant roles. 1. **Organization** — `POST /api/v1/organizations`. Your tenant account, plus lightweight directory records for counterparties (owner, GC, lender…). 2. **Division** — `POST /api/v1/divisions` (`organization_id`, `name`, full address, optional `license_number`). **The Division IS the claimant** on every document it touches. Hard cap per tier across all organizations you own: starter 1, growth 3, scale 10. Soft-deleting a division frees a slot. 3. **Project** — `POST /api/v1/projects` with **required** `division_id` and `claimant_role` (a claimant-eligible CLS role token). This also materialises the project's system-managed `is_claimant` **party**. 4. **Parties** — `POST /api/v1/parties` (`project_id`, `organization_id`, `role` — CLS's 15-token vocabulary). Add the **property owner** (required for notices, escalations, G702; waivers exempt) and the **GC** (required for preliminary notices and every escalation). Or supply them inline on the document as `metadata.owner.name` / `metadata.gc.name`. 5. **Jobsite** — `POST /api/v1/jobsites`. 6. **Document record** — create the compliance record: - Notice — `POST /api/v1/notices` (`project_id`, `organization_id`, `type` = preliminary|stop_payment|lien|bond_claim, `state`, `notice_date`, …). - Waiver — `POST /api/v1/waivers`. Escalation — `POST /api/v1/escalations`. Pay application — `POST /api/v1/pay-applications` (G702/G703; SOV via `POST /api/v1/sovs` or `POST /api/v1/sovs/import-csv`). - `division_id` is optional on all of these — it is inherited from the project. Supplying a *different* one is rejected `422`. 7. **Generate the PDF** — `POST /api/v1/documents/generate` `{ documentable_type, documentable_id, type, organization_id, preview? }`. A shared preflight asserts: a live claimant division + `is_claimant` party; an active owner (or inline `metadata.owner.name`); a GC for preliminary notices and escalations; `project.date_contract` for G702. Every document needs a complete claimant-division address (street, city, state, zip), and notices and escalations need one for every party they are served on (owner, GC, lender, surety; the owner for G702) — party organizations and inline `metadata.owner` / `metadata.gc` alike. Download: `GET /api/v1/documents/{uuid}/download`. Rework: `.../revert`. 8. **Advance workflow state** — `POST /api/v1/notices/{uuid}/transition` (and the same for `escalations`, `pay-applications`). 9. **Mail the physical parcel** — `POST /api/v1/notices/{uuid}/order` or `POST /api/v1/escalations/{uuid}/order`, or the generic `POST /api/v1/document-orders` (`order_type` = mail|filing|research|recording; `mail_class` = first_class|priority|certified). **The price is computed server-side** — never send it. Track with `GET /api/v1/document-orders/{uuid}` and webhooks. 10. **Hand research to a human** — `POST /api/v1/fulfillment-requests`, `type` = `notice_research`, with `idempotency_key`, `payload.fulfillment_scope` (research | research_and_document | full_service), `payload.division_id`, and `payload.project { … , jobsite { … } }`; optional `payload.parties[]` you already know. CLS staff research the blanks and materialise the project/notice back into your account. Poll `GET /api/v1/fulfillment-requests/{uuid}` or subscribe to webhooks. Required in `payload.project`: `furnished_description`, `contract_amount` (> 0) and the furnishing dates the jobsite state counts from (`first_furnishing_date` / `last_furnishing_date`, `date_contract` in AK/NH); optional `project_type` (CLS token, e.g. `com-new-build`, `gov-state`). ### Full-service postage A `full_service` request is researched, drafted **and mailed** by CLS, so it costs the scope fee plus postage per recipient — and nobody knows the final recipient count until research is done. 1. **Quote / 402.** On submit the API needs the fee plus postage for `max(3, count(payload.parties))` recipients. Short → `402` with `quote { fee, postage { recipients, rate, amount }, total }`, `shortfall` and `purchase.credits`. 2. **Hold.** On success the fee is charged and the postage is **held** (`data.postage { authorized_recipients, hold { amount, status } }`). CLS is told how many recipients are paid for and will not mail to more. 3. **`payment_required`.** If research finds more parties, CLS stops before mailing. The request's status becomes `payment_required` and `payment_due { required_recipients, authorized_recipients, rate, held, required_hold, additional_credits }` says what to approve. You get the `fulfillment_request.payment_required` webhook (and owners/admins an email). 4. **Approve.** `POST /api/v1/fulfillment-requests/{uuid}/approve-charges` holds `additional_credits` more and releases the ticket back to CLS. `402` (with `shortfall`) if you cannot cover it yet — buy credits and call again; `503` if CLS was unreachable (the credits stay held — call again). 5. **Settle.** When the notice is mailed you are charged postage for the recipients it actually went to and the rest of the hold is returned. A cancelled or rejected request returns the whole hold. 6. **Cancellation refunds.** When CLS cancels a request its agent decides the refund of the service fee — full, partial or none — and gives a reason. It shows on the request as `status_reason` and `refund { amount, refunded_at, reason }`, and as a refund line in your credit ledger. ### Notices — numbering, amendments and PDF options - **`notice_number`** is issued when the notice is created and never changes: `N-{yymmdd}-{org code}{###}` (e.g. `N-261001-K7Q004`) — the notice date, the issuing organization's 3-character code, and a per-organization daily counter. Pass `metadata.notice_number` to print your own number instead. - **Amend** a notice by `POST /api/v1/notices` with `parent_notice_id` + `amendment_reason` (amount_changed|scope_changed|party_correction|other). The amendment is a new notice (own lifecycle, generated and billed separately): its number is the original's plus `-A1`, `-A2`…, `original_claim_amount` defaults to the parent's `claim_amount`, and the PDF carries an amended banner and "Original Amount Claimed". - **Serve-by deadline** — send `first_furnishing_date` (and `last_furnishing_date` where the state counts from it) and omit `deadline_date`: for preliminary notices the API calculates it from the state's rule, the claimant's role and public/private work. Every notice response carries `deadline_rule` (`required`, `basis`, `days`, `deadline`, `rolling_lookback_days`, `explanation`, `source`, `verified: false`) and `deadline_source` (`calculated` | `caller` | `cls`). A deadline you send always wins; `PUT` it as `null` to go back to the calculated one. States whose deadline depends on facts the API does not hold return `deadline: null` with an explanation. The rules are unverified reference data — confirm against current statute. - **Preliminary-notice PDF options** (all in `metadata`): - `party_grid_max`: `8` (default) or `4` — party boxes on page one; extra parties go on an "Exhibit A – Additional Parties" page. - `mail_pack`: `true` adds, per served party, a cover page (addressee block + Acknowledgment of Receipt, or a Proof of Service Affidavit in CA) followed by a full copy of the notice — for mailing it yourself. `service_method` / `service_date` fill the affidavit, and an affidavit state requires `signatory_name` (a person must make the declaration). A mail-pack document cannot be mail-ordered; `POST /notices/{uuid}/order` always mails the plain notice. - `months_work_performed`: string or list — e.g. the months a Texas monthly notice ("Notice of Claim for Unpaid Labor or Materials") covers. - `hiring_party` `{name, address_line1, city, state, zip}`: who the claimant contracted with (default: the subcontractor, else GC, else owner; the owner when the claimant is the GC). - `bond_information` / `lender_information`: free text or a name + address object, shown in the surety / lender box when there is no such party. ### When to use a fulfillment request vs. generate directly - You have the parties, dates and jobsite → build the record yourself and call `documents/generate`. - You are missing the owner / GC / legal description / deadline and need a person to research it, or you want CLS to run the whole notice end to end → create a `notice_research` fulfillment request. ## Locked — do not fight these (all return 422) - **Claimant identity = the Division.** Never send `claimant_name`, `claimant_address*`, `claimant_license_number`, `claimant_email`, `claimant_*` (see `ClaimantIdentity::KEYS`) through `metadata`, `metadata.template_fields`, or `payload.claimant`. Choose the claimant with `division_id` only. `claimant_role`, `signatory_*`, `signature_date` and notary fields **are** caller-controlled. - **A document's `division_id` is always its project's.** Omit it (inherited) or send the matching one. - **The `is_claimant` party is system-managed** — cannot be deleted, and its `role` / `is_active` cannot be edited, through the API. - **A division that is a project's claimant cannot be deleted** — reassign or remove those projects first. - **Document-order and credit prices are server-authoritative** — never sent by the client. ## Webhooks - `POST /api/v1/webhooks` to subscribe. URLs pointing at private/loopback hosts are rejected in production. - Retry schedule: immediate → 10s → 1m → 10m → failure queue (max 5 attempts). - Prefer webhooks over polling for mail delivery and fulfillment status. - Fulfillment events: `fulfillment_request.submitted`, `.payment_required` (with `payment_due` and the `approve` endpoint), `.charges_approved`, `.failed`, `.rejected`. Status and research results also land on `GET /api/v1/fulfillment-requests/{uuid}` (`status`, `payment_due`, `postage`, `result`, `files[]` with `download_url`). ## Per-entity metadata / state data - Polymorphic key-value store: `GET|PUT|DELETE /api/v1/{entityType}/{entityId}/meta/{key}`. - State-specific document rules are config-driven (California implemented; other states fall back to default rules). ## Endpoint groups `organizations`, `divisions`, `contacts`, `projects`, `parties`, `jobsites`, `search`, `notices` (+ `/transition`, `/order`), `escalations` (+ `/transition`, `/order`), `sovs` (+ `/import-csv`), `pay-applications` (+ `/transition`), `waivers` (+ `/sign`), `documents` (+ `/generate`, `/{id}/revert`, `/{id}/download`), `document-orders` (+ `/cancel`, `/pay`, `/payments`), `fulfillment-requests` (+ `/cancel`, `/retry`, `/approve-charges`, `/files/{id}/download`), `webhooks`, `provider-auth`, `shipper-auth`, `audit-logs`, `sandbox` (+ `/reset`), `support/health`, `support/credits`, `support/usage`, `credits/balance|price|purchase`, `billing/plans|setup-intent|subscribe|cancel`. Full parameters, response shapes and examples: `{BASE_URL}/api/openapi.json`.