Carpet Take-offHotel flooring
Base URL
https://carpet-area.inhabitr.ai
Transport
HTTPS + JSONMultipart upload and SSE progress
Authentication
Bearer tokenAPI key or seven-day session token

Document contract

The service returns the strongest result supported by the uploaded pages.

Best effort
The service is optimized for hotel guest-room floor plans. Every readable PDF is processed; incomplete or unverifiable sets return supported measurements and inventory with unavailable fields marked for review. Room names and room-type codes may be arbitrary.

Authentication

Use an API key for services or a session token for the dashboard.

API key

Send the key in the Authorization header on every protected request.

curl https://carpet-area.inhabitr.ai/api/v1/takeoffs \
  -H "Authorization: Bearer sk_live_your_key"

Session token

Login returns a signed token valid for seven days.

curl -X POST https://carpet-area.inhabitr.ai/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"estimator","password":"your-password"}'

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "username": "estimator"
}
SSE exception: browser EventSource cannot set an Authorization header, so the events endpoint also accepts ?token=. Use HTTPS and avoid logging the query string.

Create a take-off

Upload one hotel PDF and receive a job immediately.

202 Accepted

Request

curl -X POST https://carpet-area.inhabitr.ai/api/v1/takeoffs \
  -H "Authorization: Bearer sk_live_your_key" \
  -F "file=@hotel-floor-plans.pdf" \
  -F "shadow_mode=false"

Response

{
  "job_id": "a26f62a2-6fc2-4cad-89f3-3b76a4f42c14",
  "status": "queued",
  "links": {
    "self": "/api/v1/takeoffs/a26f62a2-6fc2-4cad-89f3-3b76a4f42c14",
    "events": "/api/v1/takeoffs/a26f62a2-6fc2-4cad-89f3-3b76a4f42c14/events",
    "result": "/api/v1/takeoffs/a26f62a2-6fc2-4cad-89f3-3b76a4f42c14/result"
  }
}

The multipart request also accepts mode and shadow_mode. Trusted server integrations may submit an existing private-storage object using storage_path and pdf_name instead of a file.

Job lifecycle

Poll status or consume the event stream until the job reaches a terminal state.

uploadingqueuedrunningdone or review
uploading

The PDF is being stored. This state is normally brief.

queued

The job is waiting for an available worker.

running

The agent is validating, planning, measuring, and checking evidence.

needs_review

Quantities exist, but an advisory gate requires human review.

done

The result passed required gates or received reviewer approval.

rejected

Legacy or explicit reviewer rejection. New automated runs preserve supported output as needs_review.

error

Processing failed after automatic retries were exhausted.

cancelled

A queued or running job was cancelled by a client.

Status request

curl https://carpet-area.inhabitr.ai/api/v1/takeoffs/JOB_ID \
  -H "Authorization: Bearer sk_live_your_key"

Status response

{
  "job_id": "JOB_ID",
  "status": "needs_review",
  "pdf_name": "hotel-floor-plans.pdf",
  "created_at": "2026-07-23T18:30:00+00:00",
  "duration_ms": 21482,
  "error": null,
  "summary": {
    "net_area": 34341,
    "order_area": 37775,
    "room_count": 130,
    "confidence": "high"
  }
}

Server-sent events

Receive ordered progress logs followed by one terminal event.

text/event-stream

Connect

curl -N "https://carpet-area.inhabitr.ai/api/v1/takeoffs/JOB_ID/events?token=sk_live_your_key"

Event stream

event: log
data: {"elapsed_ms":842,"level":"info","message":"Document preflight passed"}

event: log
data: {"elapsed_ms":19420,"level":"tool","message":"Quality gates complete"}

event: needs_review
data: {"job_id":"JOB_ID","status":"needs_review","duration_ms":21482}

Log events use event name log. The final event name matches the terminal status: done, needs_review, rejected, error, or cancelled. Reconnect by reading current job status first because the stream does not expose an event cursor.

Human review

Reviewer actions preserve agent judgment while controlling unsupported conclusions.

Approve a result

curl -X POST https://carpet-area.inhabitr.ai/api/v1/takeoffs/JOB_ID/review \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "approve",
    "notes": "Scale and room count verified.",
    "corrections": {}
  }'

Response

{
  "job_id": "JOB_ID",
  "action": "approve",
  "status": "done"
}
  • approve changes needs_review to done.
  • request_reanalysis returns the job to queued.
  • comment records notes without changing status.
  • reject makes the job terminal.

Generate a report

Customer reports are validated against the take-off JSON before publication.

Request

curl -X POST https://carpet-area.inhabitr.ai/api/v1/takeoffs/JOB_ID/report \
  -H "Authorization: Bearer sk_live_your_key"

The job must be done. Reviewer-approved results may retain advisory evidence notes in the generated report.

Response

{
  "report_url": "https://carpet-area.inhabitr.ai/api/v1/takeoffs/JOB_ID/report/view?token=...",
  "validation": {
    "outcome": "needs_review",
    "review_gates": 1
  }
}
Report validation blocks schema, arithmetic, floor coverage, encoding, or rendering failures. Advisory evidence gaps do not replace agent judgment. They are disclosed in the report as measured, estimated, assumed, or unavailable evidence. The returned URL is served by the API with a report-specific token and expires after 24 hours.

Errors

Non-2xx responses use a consistent JSON detail field.

{
  "detail": "The PDF exceeds the 25 MB upload limit."
}
StatusMeaningTypical cause
400Invalid requestMissing upload input, unsupported review action, or malformed request data.
401UnauthorizedThe Bearer credential or report-specific access token is missing, invalid, inactive, or expired.
404Not foundThe job, result, metrics, or learning record does not exist or is not ready.
409Invalid stateThe action conflicts with job state, or report validation found inconsistent quantities.
413Upload too largeThe PDF is larger than 25 MB.
415Unsupported mediaThe upload is not a PDF or does not contain a valid PDF signature.
422Validation failedA typed path, query, form, or JSON field failed request validation.
500Server errorAn unexpected service error occurred. Retry only idempotent requests automatically.

Limits and pagination

List endpoints use bounded offset pagination and include navigation metadata.

25 MBMaximum PDF upload
50Default list page size
100Maximum take-off page size
200Maximum learning-hint page size
365 daysMaximum metrics window
24 hoursSigned report URL lifetime

Page request

curl "https://carpet-area.inhabitr.ai/api/v1/takeoffs?limit=2&offset=0" \
  -H "Authorization: Bearer sk_live_your_key"

Pagination metadata

{
  "takeoffs": [
    {"id":"JOB_ID_1","pdf_name":"hotel-a.pdf","status":"done"},
    {"id":"JOB_ID_2","pdf_name":"hotel-b.pdf","status":"needs_review"}
  ],
  "pagination": {
    "limit": 2,
    "offset": 0,
    "count": 2,
    "has_more": true,
    "next_offset": 2
  }
}

Continue while has_more is true and pass next_offset to the next request. Negative offsets are treated as zero. Oversized limits are reduced to the endpoint maximum. The service does not currently publish rate-limit headers; clients should still handle 429 and transient 5xx responses with bounded exponential backoff.

Endpoint reference

Protected endpoints require Bearer authentication unless marked public.

GroupMethodPathDescription
Take-offsPOST/api/v1/takeoffsUpload a hotel floor-plan PDF. Returns 202 with a queued job.
Take-offsGET/api/v1/takeoffsList take-offs with summaries and offset pagination.
Take-offsGET/api/v1/takeoffs/:idRead job status, rejection details, links, and summary quantities.
Take-offsGET/api/v1/takeoffs/:id/resultRead the schema-conformant take-off JSON when a result is available.
Take-offsGET/api/v1/takeoffs/:id/eventsStream progress logs and the terminal job state over SSE.
ReviewPOST/api/v1/takeoffs/:id/reviewApprove, reject, comment, or request evidence-guided reanalysis.
ReviewPOST/api/v1/takeoffs/:id/cancelCancel a queued or running job.
ReviewPOST/api/v1/takeoffs/:id/reportValidate and generate a signed HTML report URL for an approved result.
ReviewGET/api/v1/takeoffs/:id/report/viewRender a generated report using its short-lived report token.
ObservabilityGET/api/v1/takeoffs/:id/metricsRead stage, agent, render DPI, resource fallback, evidence, cache, plan, review, and gate telemetry.
ObservabilityGET/api/v1/metrics/overviewRead dashboard aggregates, stage latency, outcomes, and recent telemetry.
Learning hintsGET/api/v1/learningsList candidate, approved, and disabled reusable hints with pagination.
Learning hintsPOST/api/v1/learnings/:id/approveApprove an evidence-backed candidate as a non-binding hint.
Learning hintsPOST/api/v1/learnings/:id/disableDisable a reusable hint.
PublicGET/healthzRead service health and version without authentication.
PublicGET/api/v1/schemaRead the JSON Schema for take-off results without authentication.

Result contract

Every quantity remains reviewable and traceable to its measurement evidence.

Retrieve a result

curl https://carpet-area.inhabitr.ai/api/v1/takeoffs/JOB_ID/result \
  -H "Authorization: Bearer sk_live_your_key"

A result may be retrieved for done and needs_review jobs. Rejected documents have no result.

Core shape

{
  "schema_version": "1.0.0",
  "property": {
    "name": "Example Hotel",
    "source_document": {
      "filename": "hotel-floor-plans.pdf",
      "page_count": 18
    }
  },
  "takeoff": {
    "unit_system": "imperial",
    "area_unit": "sqft",
    "target_finish": "carpet",
    "grounding": {},
    "buildings": [],
    "totals": {
      "net_area": 34341,
      "waste_factor": 0.1,
      "order_area": 37775,
      "unit": "sqft",
      "guest_room_count": 130,
      "by_category": {},
      "by_room_type": {},
      "by_floor": {}
    }
  }
}
Read the authoritative JSON Schema from GET /api/v1/schema. Unknown room-type labels are preserved in takeoff.totals.by_room_type; they are not used to decide whether the document is a supported hotel plan.