Skip to main content
Quote blocks are the configurable line items that appear in the public quotation builder. Each block belongs to a category. Blocks and categories can be managed through web routes (session auth) or API routes (Bearer token).

Web routes

Session-authenticated form-based endpoints at /bloques/*. Return HTML views or redirects.

API routes

Sanctum Bearer token endpoints at /api/v1/admin/quote-blocks/*. Return JSON.
Web routes require the auth + verified middleware. API routes require a valid Sanctum Bearer token and are throttled at 30 requests per minute.

Web routes

List blocks

GET /bloques Returns an HTML view listing all categories (ordered by order) with their blocks (each category’s blocks ordered by order).
cURL
curl --request GET \
  --url https://your-app.test/bloques \
  --cookie 'laravel_session=<session_cookie>'

Create block form

GET /bloques/create Returns the HTML form to create a new block. The form is pre-populated with all available categories.
cURL
curl --request GET \
  --url https://your-app.test/bloques/create \
  --cookie 'laravel_session=<session_cookie>'

Store block

POST /bloques Validates and persists a new quote block. The block’s order is set to max(order) + 1 within the chosen category. is_active defaults to true if omitted.
name
string
required
Display name of the block. Maximum 255 characters.
category_id
integer
required
ID of an existing row in quote_block_categories. Must pass an exists check.
base_price
number
required
Base price for the block. Must be numeric and >= 0.
default_hours
integer
required
Default estimated hours. Must be an integer >= 0.
description
string
Optional free-text description.
is_active
boolean
default:"true"
Whether the block is visible in the public builder. Treated as a boolean checkbox — the server calls $request->boolean('is_active', true).
extras
object[]
Key/value pairs stored in the JSON config column. Each item must have a key and a value. Numeric values are cast to numbers; all others remain strings.
cURL
curl --request POST \
  --url https://your-app.test/bloques \
  --header 'Content-Type: application/json' \
  --header 'X-CSRF-TOKEN: <token>' \
  --cookie 'laravel_session=<session_cookie>' \
  --data '{
    "name": "Landing Page",
    "category_id": 3,
    "base_price": 1200,
    "default_hours": 24,
    "description": "Single-page marketing site",
    "is_active": true,
    "extras": [
      {"key": "revision_rounds", "value": "2"},
      {"key": "cms_included", "value": "false"}
    ]
  }'
On success: redirects to /bloques with a success flash. On validation failure: redirects back with errors and input preserved.

Edit block form

GET /bloques/{bloque}/edit Returns the HTML edit form pre-populated with the block’s current values.
bloque
integer
required
The block’s primary key. Laravel’s route model binding uses the bloque parameter name.
cURL
curl --request GET \
  --url https://your-app.test/bloques/7/edit \
  --cookie 'laravel_session=<session_cookie>'

Update block

PUT /bloques/{bloque} or PATCH /bloques/{bloque} Updates an existing block. Applies the same validation rules as store. order is not updated by this endpoint — use the reorder API route for that.
bloque
integer
required
The block’s primary key.
name
string
required
Display name. Maximum 255 characters.
category_id
integer
required
Must reference a valid quote_block_categories.id.
base_price
number
required
Must be numeric and >= 0.
default_hours
integer
required
Must be an integer >= 0.
description
string
Optional free-text description.
is_active
boolean
default:"true"
Active state.
extras
object[]
Replaces the entire config JSON column. Same shape as the store endpoint.
cURL
curl --request PUT \
  --url https://your-app.test/bloques/7 \
  --header 'Content-Type: application/json' \
  --header 'X-CSRF-TOKEN: <token>' \
  --cookie 'laravel_session=<session_cookie>' \
  --data '{
    "name": "Landing Page Pro",
    "category_id": 3,
    "base_price": 1500,
    "default_hours": 30,
    "is_active": true
  }'
On success: redirects to /bloques with a success flash. On validation failure: redirects back with errors and input preserved.

Delete block

DELETE /bloques/{bloque} Permanently deletes the block.
bloque
integer
required
The block’s primary key.
This is a hard delete. Deleted blocks cannot be recovered via the UI. Historical quote items that reference the block via quote_block_id are not deleted but lose their block association.
cURL
curl --request DELETE \
  --url https://your-app.test/bloques/7 \
  --header 'X-CSRF-TOKEN: <token>' \
  --cookie 'laravel_session=<session_cookie>'
On success: redirects to /bloques with a success flash.

Create category

POST /bloques/categorias Creates a new block category. The category’s order is set to max(order) + 1 globally. is_active is always set to true on creation.
name
string
required
Category name. Must be unique across quote_block_categories.name. Maximum 255 characters.
description
string
Optional description. Maximum 500 characters.
Validation errors for this form are returned under the category error bag (not the default bag), so they can be rendered separately from block form errors on the same page.
cURL
curl --request POST \
  --url https://your-app.test/bloques/categorias \
  --header 'Content-Type: application/json' \
  --header 'X-CSRF-TOKEN: <token>' \
  --cookie 'laravel_session=<session_cookie>' \
  --data '{
    "name": "E-Commerce",
    "description": "Online store and payment integration modules"
  }'
On success: redirects to /bloques with a success flash. On validation failure: redirects back with category error bag errors and input preserved.

API routes (Bearer token)

All routes are prefixed /api/v1/admin/ and require Authorization: Bearer <token> with the auth:sanctum middleware.

List all blocks

GET /api/v1/admin/quote-blocks Returns all quote blocks.
cURL
curl --request GET \
  --url https://your-app.test/api/v1/admin/quote-blocks \
  --header 'Authorization: Bearer <your-token>' \
  --header 'Accept: application/json'

Create block

POST /api/v1/admin/quote-blocks
cURL
curl --request POST \
  --url https://your-app.test/api/v1/admin/quote-blocks \
  --header 'Authorization: Bearer <your-token>' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{
    "name": "SEO Package",
    "category_id": 2,
    "base_price": 800,
    "default_hours": 16,
    "is_active": true
  }'

Get block

GET /api/v1/admin/quote-blocks/{id}
id
integer
required
The block’s primary key.
cURL
curl --request GET \
  --url https://your-app.test/api/v1/admin/quote-blocks/7 \
  --header 'Authorization: Bearer <your-token>' \
  --header 'Accept: application/json'
id
integer
Primary key.
name
string
Block display name.
description
string | null
Optional description.
category_id
integer
Foreign key to quote_block_categories.
base_price
string
Base price (decimal:2).
default_hours
integer
Estimated hours.
config
array
Array of {key: value} configuration pairs.
is_active
boolean
Whether the block is visible in the builder.
order
integer
Display sort order within the category.
created_at
string
ISO 8601 timestamp.
updated_at
string
ISO 8601 timestamp.
Response example
{
  "id": 7,
  "name": "Landing Page",
  "description": "Single-page marketing site",
  "category_id": 3,
  "base_price": "1200.00",
  "default_hours": 24,
  "config": [
    {"revision_rounds": 2},
    {"cms_included": "false"}
  ],
  "is_active": true,
  "order": 1,
  "created_at": "2026-03-01T10:00:00.000000Z",
  "updated_at": "2026-03-15T08:30:00.000000Z"
}

Update block

PUT /api/v1/admin/quote-blocks/{id}
id
integer
required
The block’s primary key.
cURL
curl --request PUT \
  --url https://your-app.test/api/v1/admin/quote-blocks/7 \
  --header 'Authorization: Bearer <your-token>' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{
    "name": "Landing Page Pro",
    "category_id": 3,
    "base_price": 1500,
    "default_hours": 30,
    "is_active": true
  }'

Delete block

DELETE /api/v1/admin/quote-blocks/{id}
id
integer
required
The block’s primary key.
Hard delete. Historical quote items that reference this block via quote_block_id lose their block association.
cURL
curl --request DELETE \
  --url https://your-app.test/api/v1/admin/quote-blocks/7 \
  --header 'Authorization: Bearer <your-token>' \
  --header 'Accept: application/json'

Reorder blocks

POST /api/v1/admin/quote-blocks/reorder Updates the order field for one or more blocks in a single request. Each block in the payload is updated individually via a targeted UPDATE query.
blocks
object[]
required
Array of reorder instructions. Every listed block is updated; unlisted blocks are untouched.
cURL
curl --request POST \
  --url https://your-app.test/api/v1/admin/quote-blocks/reorder \
  --header 'Authorization: Bearer <your-token>' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{
    "blocks": [
      {"id": 7, "order": 1},
      {"id": 3, "order": 2},
      {"id": 12, "order": 3}
    ]
  }'
success
boolean
Always true on success.
Response
{"success": true}

QuoteBlock model fields reference

FieldTypeNotes
idintegerAuto-increment primary key
namestringRequired, max 255
descriptionstring|nullOptional
category_idintegerFK → quote_block_categories.id
base_pricedecimal:2Min 0
default_hoursintegerMin 0
configarray (JSON)Stored as [{key: value}, ...]
is_activebooleanFilters visible blocks in the builder
orderintegerSort order within category

QuoteBlockCategory model fields reference

FieldTypeNotes
idintegerAuto-increment primary key
namestringRequired, unique, max 255
descriptionstring|nullOptional, max 500
iconstring|nullDisplay icon identifier
colorstring|nullDisplay color value
orderintegerGlobal sort order
is_activebooleanAlways true on creation via UI

Build docs developers (and LLMs) love