{"openapi":"3.0.3","info":{"title":"Home Remodel Pro API","version":"2026-10-01","description":"Read a franchise network’s contacts, leads, jobs, estimates, invoices, payments and photographs.\n\n**Versioning.** Send `X-HRP-VERSION` on every request. A version is served for at least twelve months after it is superseded, and a deprecation is announced at least ninety days before the sunset — in `Deprecation` and `Sunset` headers and in `meta.warnings`.\n\n**Rate limits.** 2,500 requests per 5 minutes per authorization, and a 10,000-unit weighted cost budget over the same window. Both are reported in `X-HRP-RateLimit-*` and `X-HRP-Cost-*` headers, and the cost of the request you just made is in `meta.cost`.\n\n**Scope.** A token can never do more than the seat that authorised it. Close that seat and the token stops working the same day.\n\n**No CORS.** Tokens belong on a server. A browser cannot call this API cross-origin, deliberately."},"servers":[{"url":"https://homeremodelpro.com","description":"Production"}],"components":{"securitySchemes":{"oauth2":{"type":"oauth2","description":"Authorization code with PKCE (S256, required). Access tokens last 60 minutes; refresh tokens rotate, and presenting a used one revokes the whole chain.","flows":{"authorizationCode":{"authorizationUrl":"https://homeremodelpro.com/oauth/authorize","tokenUrl":"https://homeremodelpro.com/api/hrp/oauth/token","refreshUrl":"https://homeremodelpro.com/api/hrp/oauth/token","scopes":{"contacts.read":"Read your customers and their properties","leads.read":"Read your leads and where each one came from","leads.write":"Create new leads in your pipeline","projects.read":"Read your jobs, their phases and their contract values","estimates.read":"Read your estimates and signed contracts","invoices.read":"Read your invoices and what has been paid","payments.read":"Read payments and refunds against your invoices","media.read":"Read the photographs taken on your jobs","webhooks.write":"Subscribe its own server to your events, and unsubscribe again","franchises.read":"Read the locations in this network"}}}}},"schemas":{"Problem":{"type":"object","description":"RFC 7807. Every refusal has this shape and a `trace_id` to quote.","properties":{"type":{"type":"string","description":"One of https://docs.homeremodelpro.com/api/problems/…"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"trace_id":{"type":"string"}},"required":["type","title","status","detail","trace_id"]},"Contact":{"type":"object","description":"The households a location works with. One household is one contact (see `merged_into`).","properties":{"id":{"type":"string","description":"ULID."},"franchise_id":{"type":"string","description":"The location that owns this contact."},"name":{"type":"string","description":"The household name."},"email":{"type":"string","nullable":true,"description":"As it was typed."},"phone":{"type":"string","nullable":true,"description":"As it was typed."},"source":{"type":"string","description":"How the contact first arrived."},"sms_opt_out":{"type":"boolean","description":"True once they replied STOP. Nothing may text them."},"merged_into":{"type":"string","nullable":true,"description":"Set when this contact was merged away. Follow it: the survivor is the live record."},"created_at":{"type":"string","format":"date-time","description":"When the record was created."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. Poll this with `updated_since`."},"links":{"type":"object","properties":{"self":{"type":"string"}},"required":["self"]}},"required":["id","franchise_id","name","source","sms_opt_out","created_at","updated_at","links"]},"Lead":{"type":"object","description":"Enquiries, with where each came from and the five-minute first-response clock. A lead with no `franchise_id` is unclaimed and only head office sees it.","properties":{"id":{"type":"string","description":"ULID."},"franchise_id":{"type":"string","nullable":true,"description":"Null while the lead is unclaimed."},"contact_id":{"type":"string","nullable":true,"description":"Filled once the lead has been linked to a household."},"stage":{"type":"string","description":"Where it is in the pipeline."},"lost_reason":{"type":"string","nullable":true,"description":"Required when the stage is `lost`."},"name":{"type":"string","description":"The homeowner’s name."},"email":{"type":"string","nullable":true,"description":"As it was submitted."},"phone":{"type":"string","nullable":true,"description":"As it was submitted."},"zip5":{"type":"string","nullable":true,"description":"The ZIP the lead was routed on."},"city":{"type":"string","nullable":true,"description":"As it was submitted."},"state":{"type":"string","nullable":true,"description":"As it was submitted."},"project_type":{"type":"string","nullable":true,"description":"What the homeowner asked about."},"message":{"type":"string","nullable":true,"description":"What they wrote."},"source":{"type":"string","description":"Which door it came through."},"utm_source":{"type":"string","nullable":true,"description":"First-touch attribution."},"utm_medium":{"type":"string","nullable":true,"description":"First-touch attribution."},"utm_campaign":{"type":"string","nullable":true,"description":"First-touch attribution."},"referrer":{"type":"string","nullable":true,"description":"The page that linked to the form."},"landing_url":{"type":"string","nullable":true,"description":"The page the form was on."},"routing":{"type":"string","description":"How it reached a location: `territory`, `adjacency` or `unclaimed`."},"routing_note":{"type":"string","nullable":true,"description":"Why, in a sentence. Always set when `routing` is `unclaimed`."},"sla_first_response_at":{"type":"string","format":"date-time","description":"When the first response is due. Set at capture and never moved."},"first_response_at":{"type":"string","format":"date-time","nullable":true,"description":"When somebody answered. Stamped once."},"created_at":{"type":"string","format":"date-time","description":"When the record was created."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. Poll this with `updated_since`."},"links":{"type":"object","properties":{"self":{"type":"string"}},"required":["self"]}},"required":["id","stage","name","source","routing","sla_first_response_at","created_at","updated_at","links"]},"Project":{"type":"object","description":"Jobs under contract, with the contract value and the planned dates.","properties":{"id":{"type":"string","description":"ULID."},"franchise_id":{"type":"string","description":"The location running the job."},"contact_id":{"type":"string","nullable":true,"description":"Whose house it is."},"property_id":{"type":"string","nullable":true,"description":"Which of their properties."},"name":{"type":"string","description":"What the job is called."},"type":{"type":"string","description":"The remodel type."},"status":{"type":"string","description":"Where the job is."},"status_reason":{"type":"string","nullable":true,"description":"Why, when the status needs one."},"contract_value_cents":{"type":"integer","format":"int64","description":"Integer cents, including signed change orders."},"starts_on":{"type":"string","format":"date","nullable":true,"description":"Planned first day on site."},"planned_end":{"type":"string","format":"date","nullable":true,"description":"Planned last day, counted in working days."},"created_at":{"type":"string","format":"date-time","description":"When the record was created."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. Poll this with `updated_since`."},"links":{"type":"object","properties":{"self":{"type":"string"}},"required":["self"]}},"required":["id","franchise_id","name","type","status","contract_value_cents","created_at","updated_at","links"]},"Estimate":{"type":"object","description":"Quotes and signed contracts. An accepted estimate is immutable; a change is a new `version` that supersedes it.","properties":{"id":{"type":"string","description":"ULID."},"franchise_id":{"type":"string","description":"The location that quoted."},"lead_id":{"type":"string","nullable":true,"description":"The enquiry behind it."},"contact_id":{"type":"string","nullable":true,"description":"Whose house it is."},"project_id":{"type":"string","nullable":true,"description":"The job it opened, once signed."},"version":{"type":"integer","format":"int64","description":"Revisions count up; the superseded one stays."},"supersedes_id":{"type":"string","nullable":true,"description":"The estimate this one replaced."},"status":{"type":"string","description":"Where the quote is."},"title":{"type":"string","description":"What was quoted."},"project_type":{"type":"string","description":"The remodel type."},"sent_at":{"type":"string","format":"date-time","nullable":true,"description":"When it went to the homeowner."},"viewed_at":{"type":"string","format":"date-time","nullable":true,"description":"When they first opened it."},"decided_at":{"type":"string","format":"date-time","nullable":true,"description":"When they accepted or declined."},"accepted_tier":{"type":"string","nullable":true,"description":"Which option they signed."},"accepted_total_cents":{"type":"integer","format":"int64","nullable":true,"description":"Integer cents. Null until signed."},"decline_reason":{"type":"string","nullable":true,"description":"What they said."},"created_at":{"type":"string","format":"date-time","description":"When the record was created."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. Poll this with `updated_since`."},"links":{"type":"object","properties":{"self":{"type":"string"}},"required":["self"]}},"required":["id","franchise_id","version","status","title","project_type","created_at","updated_at","links"]},"Invoice":{"type":"object","description":"`paid_cents` is recomputed from the payments and refunds that exist — there is no way to mark an invoice paid, here or anywhere.","properties":{"id":{"type":"string","description":"ULID."},"franchise_id":{"type":"string","description":"The location that issued it."},"project_id":{"type":"string","nullable":true,"description":"The job it is against."},"estimate_id":{"type":"string","nullable":true,"description":"The contract it came from."},"contact_id":{"type":"string","nullable":true,"description":"Who owes it."},"number":{"type":"string","description":"The human-readable number."},"kind":{"type":"string","description":"Deposit, draw, change order, final or retention."},"status":{"type":"string","description":"Where it is."},"amount_cents":{"type":"integer","format":"int64","description":"Integer cents."},"paid_cents":{"type":"integer","format":"int64","description":"Integer cents. A sum of the payments, never a status."},"due_at":{"type":"string","format":"date-time","nullable":true,"description":"When payment is due."},"issued_at":{"type":"string","format":"date-time","nullable":true,"description":"When it went out."},"created_at":{"type":"string","format":"date-time","description":"When the record was created."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. Poll this with `updated_since`."},"links":{"type":"object","properties":{"self":{"type":"string"}},"required":["self"]}},"required":["id","franchise_id","number","kind","status","amount_cents","paid_cents","created_at","updated_at","links"]},"Payment":{"type":"object","description":"Money received against an invoice. A refund is a separate reversing record and is not listed here; it shows up in the invoice’s `paid_cents`.","properties":{"id":{"type":"string","description":"ULID."},"franchise_id":{"type":"string","description":"The location that was paid."},"invoice_id":{"type":"string","description":"What it paid."},"project_id":{"type":"string","nullable":true,"description":"The job, for P&L."},"provider":{"type":"string","description":"Who moved the money, or `manual` for a cheque."},"method":{"type":"string","description":"Card, ACH, cheque, cash or other."},"amount_cents":{"type":"integer","format":"int64","description":"Integer cents."},"fee_cents":{"type":"integer","format":"int64","description":"Integer cents the provider kept."},"status":{"type":"string","description":"ACH delivers `pending` and `succeeded` days apart."},"received_on":{"type":"string","format":"date","nullable":true,"description":"The day it landed."},"failure_reason":{"type":"string","nullable":true,"description":"Set when the status is `failed`."},"created_at":{"type":"string","format":"date-time","description":"When the record was created."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. Poll this with `updated_since`."},"links":{"type":"object","properties":{"self":{"type":"string"}},"required":["self"]}},"required":["id","franchise_id","invoice_id","provider","method","amount_cents","fee_cents","status","created_at","updated_at","links"]},"MediaAsset":{"type":"object","description":"Photographs from the field. The bytes are not public: `storage_key` is deliberately not exposed, and a file is fetched through `/api/hrp/capture/file` with a credential the request already carries. Design concepts are not served here: a generated image of unbought work is not a record of the job.","properties":{"id":{"type":"string","description":"ULID, minted on the device — it is the idempotency key end to end."},"franchise_id":{"type":"string","description":"The location that took it."},"project_id":{"type":"string","nullable":true,"description":"The job it is of."},"phase_id":{"type":"string","nullable":true,"description":"The phase it is of."},"kind":{"type":"string","description":"Photo or video. Design concepts are a different thing and are not served here."},"content_type":{"type":"string","description":"The media type."},"bytes":{"type":"integer","format":"int64","description":"Size of the stored file."},"width":{"type":"integer","format":"int64","nullable":true,"description":"Pixels."},"height":{"type":"integer","format":"int64","nullable":true,"description":"Pixels."},"captured_at":{"type":"string","format":"date-time","description":"The device’s clock — which can be wrong."},"received_at":{"type":"string","format":"date-time","nullable":true,"description":"Ours. Kept apart from the device’s on purpose."},"state":{"type":"string","description":"A capture is not evidence until this is `stored`."},"tags":{"type":"array","items":{"type":"string"},"description":"Lowercased and de-duplicated. Filtering by two means both."},"note":{"type":"string","nullable":true,"description":"What the photographer wrote."},"shared_at":{"type":"string","format":"date-time","nullable":true,"description":"When the builder shared it with the homeowner. Null is silence."},"created_at":{"type":"string","format":"date-time","description":"When the record was created."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. Poll this with `updated_since`."},"links":{"type":"object","properties":{"self":{"type":"string"}},"required":["self"]}},"required":["id","franchise_id","kind","content_type","bytes","captured_at","state","tags","created_at","updated_at","links"]},"Franchise":{"type":"object","description":"The locations in the network. Only an organization-scope token sees more than one.","properties":{"id":{"type":"string","description":"ULID."},"name":{"type":"string","description":"The location’s name."},"slug":{"type":"string","description":"Its URL segment."},"status":{"type":"string","description":"Onboarding, active, suspended or closed."},"timezone":{"type":"string","description":"IANA zone. A month is a month in the location’s own zone."},"launched_at":{"type":"string","format":"date-time","nullable":true,"description":"When the launch track finished. Null while launching."},"created_at":{"type":"string","format":"date-time","description":"When the record was created."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. Poll this with `updated_since`."},"links":{"type":"object","properties":{"self":{"type":"string"}},"required":["self"]}},"required":["id","name","slug","status","timezone","created_at","updated_at","links"]},"Meta":{"type":"object","description":"What this request cost against the 10000-unit budget in the current 5-minute window.","properties":{"version":{"type":"string"},"cost":{"type":"object","properties":{"requested":{"type":"integer"},"remaining":{"type":"integer"}},"required":["requested","remaining"]},"warnings":{"type":"array","items":{"type":"string"}}},"required":["version","cost"]}}},"security":[{"oauth2":[]}],"paths":{"/v1/contacts":{"get":{"operationId":"listContacts","summary":"List contacts","description":"The households a location works with. One household is one contact (see `merged_into`).","security":[{"oauth2":["contacts.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Rows per page. Asking for more than 100 is refused rather than clamped."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque. Send back `page.next_cursor` unchanged; it is a key, never an offset."},{"name":"updated_since","in":"query","schema":{"type":"string"},"description":"RFC 3339 timestamp. Only records changed at or after this moment."}],"responses":{"200":{"description":"A page of contacts.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"page":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true}},"required":["next_cursor"]},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","page","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/contacts/{id}":{"get":{"operationId":"getContact","summary":"Read one contact","description":"A contact in another tenant and one that never existed are the same answer: 404.","security":[{"oauth2":["contacts.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ULID."}],"responses":{"200":{"description":"One contact.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/leads":{"get":{"operationId":"listLeads","summary":"List leads","description":"Enquiries, with where each came from and the five-minute first-response clock. A lead with no `franchise_id` is unclaimed and only head office sees it.","security":[{"oauth2":["leads.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Rows per page. Asking for more than 100 is refused rather than clamped."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque. Send back `page.next_cursor` unchanged; it is a key, never an offset."},{"name":"stage","in":"query","schema":{"type":"string","enum":["new","contacted","walkthrough_scheduled","estimating","proposal_sent","nurturing","won","lost"]},"description":"Pipeline stage."},{"name":"source","in":"query","schema":{"type":"string","enum":["hosted_form","embed","api","manual"]},"description":"Which door the lead came through."},{"name":"updated_since","in":"query","schema":{"type":"string"},"description":"RFC 3339 timestamp. Only records changed at or after this moment."}],"responses":{"200":{"description":"A page of leads.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Lead"}},"page":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true}},"required":["next_cursor"]},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","page","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/leads/{id}":{"get":{"operationId":"getLead","summary":"Read one lead","description":"A lead in another tenant and one that never existed are the same answer: 404.","security":[{"oauth2":["leads.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ULID."}],"responses":{"200":{"description":"One lead.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Lead"},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects":{"get":{"operationId":"listProjects","summary":"List projects","description":"Jobs under contract, with the contract value and the planned dates.","security":[{"oauth2":["projects.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Rows per page. Asking for more than 100 is refused rather than clamped."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque. Send back `page.next_cursor` unchanged; it is a key, never an offset."},{"name":"status","in":"query","schema":{"type":"string","enum":["pending_start","in_progress","on_hold","punch_list","completed","closed","cancelled"]},"description":"Job status."},{"name":"type","in":"query","schema":{"type":"string","enum":["kitchen","bath","basement","addition","whole_home","other"]},"description":"Remodel type."},{"name":"updated_since","in":"query","schema":{"type":"string"},"description":"RFC 3339 timestamp. Only records changed at or after this moment."}],"responses":{"200":{"description":"A page of projects.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Project"}},"page":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true}},"required":["next_cursor"]},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","page","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{id}":{"get":{"operationId":"getProject","summary":"Read one project","description":"A project in another tenant and one that never existed are the same answer: 404.","security":[{"oauth2":["projects.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ULID."}],"responses":{"200":{"description":"One project.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Project"},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/estimates":{"get":{"operationId":"listEstimates","summary":"List estimates","description":"Quotes and signed contracts. An accepted estimate is immutable; a change is a new `version` that supersedes it.","security":[{"oauth2":["estimates.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Rows per page. Asking for more than 100 is refused rather than clamped."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque. Send back `page.next_cursor` unchanged; it is a key, never an offset."},{"name":"status","in":"query","schema":{"type":"string","enum":["draft","sent","viewed","accepted","declined","expired","superseded"]},"description":"Quote status."},{"name":"updated_since","in":"query","schema":{"type":"string"},"description":"RFC 3339 timestamp. Only records changed at or after this moment."}],"responses":{"200":{"description":"A page of estimates.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Estimate"}},"page":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true}},"required":["next_cursor"]},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","page","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/estimates/{id}":{"get":{"operationId":"getEstimate","summary":"Read one estimate","description":"A estimate in another tenant and one that never existed are the same answer: 404.","security":[{"oauth2":["estimates.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ULID."}],"responses":{"200":{"description":"One estimate.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Estimate"},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/invoices":{"get":{"operationId":"listInvoices","summary":"List invoices","description":"`paid_cents` is recomputed from the payments and refunds that exist — there is no way to mark an invoice paid, here or anywhere.","security":[{"oauth2":["invoices.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Rows per page. Asking for more than 100 is refused rather than clamped."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque. Send back `page.next_cursor` unchanged; it is a key, never an offset."},{"name":"status","in":"query","schema":{"type":"string","enum":["draft","open","partially_paid","paid","void","uncollectible"]},"description":"Invoice status."},{"name":"kind","in":"query","schema":{"type":"string","enum":["deposit","draw","change_order","final"]},"description":"What the invoice is for."},{"name":"updated_since","in":"query","schema":{"type":"string"},"description":"RFC 3339 timestamp. Only records changed at or after this moment."}],"responses":{"200":{"description":"A page of invoices.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}},"page":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true}},"required":["next_cursor"]},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","page","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/invoices/{id}":{"get":{"operationId":"getInvoice","summary":"Read one invoice","description":"A invoice in another tenant and one that never existed are the same answer: 404.","security":[{"oauth2":["invoices.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ULID."}],"responses":{"200":{"description":"One invoice.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/payments":{"get":{"operationId":"listPayments","summary":"List payments","description":"Money received against an invoice. A refund is a separate reversing record and is not listed here; it shows up in the invoice’s `paid_cents`.","security":[{"oauth2":["payments.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Rows per page. Asking for more than 100 is refused rather than clamped."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque. Send back `page.next_cursor` unchanged; it is a key, never an offset."},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","succeeded","failed"]},"description":"Payment status."},{"name":"updated_since","in":"query","schema":{"type":"string"},"description":"RFC 3339 timestamp. Only records changed at or after this moment."}],"responses":{"200":{"description":"A page of payments.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Payment"}},"page":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true}},"required":["next_cursor"]},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","page","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/payments/{id}":{"get":{"operationId":"getPayment","summary":"Read one payment","description":"A payment in another tenant and one that never existed are the same answer: 404.","security":[{"oauth2":["payments.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ULID."}],"responses":{"200":{"description":"One payment.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Payment"},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/media":{"get":{"operationId":"listMedia","summary":"List media","description":"Photographs from the field. The bytes are not public: `storage_key` is deliberately not exposed, and a file is fetched through `/api/hrp/capture/file` with a credential the request already carries. Design concepts are not served here: a generated image of unbought work is not a record of the job.","security":[{"oauth2":["media.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Rows per page. Asking for more than 100 is refused rather than clamped."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque. Send back `page.next_cursor` unchanged; it is a key, never an offset."},{"name":"kind","in":"query","schema":{"type":"string","enum":["photo","video"]},"description":"Photo or video."},{"name":"project_id","in":"query","schema":{"type":"string"},"description":"Only captures on this job."},{"name":"updated_since","in":"query","schema":{"type":"string"},"description":"RFC 3339 timestamp. Only records changed at or after this moment."}],"responses":{"200":{"description":"A page of media.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MediaAsset"}},"page":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true}},"required":["next_cursor"]},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","page","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/media/{id}":{"get":{"operationId":"getMediaAsset","summary":"Read one media asset","description":"A media asset in another tenant, one that never existed, and one outside this resource are the same answer: 404. Design concepts are not served here: a generated image of unbought work is not a record of the job.","security":[{"oauth2":["media.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ULID."}],"responses":{"200":{"description":"One media asset.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MediaAsset"},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/franchises":{"get":{"operationId":"listFranchises","summary":"List franchises","description":"The locations in the network. Only an organization-scope token sees more than one.","security":[{"oauth2":["franchises.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Rows per page. Asking for more than 100 is refused rather than clamped."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque. Send back `page.next_cursor` unchanged; it is a key, never an offset."},{"name":"status","in":"query","schema":{"type":"string","enum":["onboarding","active","suspended","closed"]},"description":"Location status."},{"name":"updated_since","in":"query","schema":{"type":"string"},"description":"RFC 3339 timestamp. Only records changed at or after this moment."}],"responses":{"200":{"description":"A page of franchises.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Franchise"}},"page":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true}},"required":["next_cursor"]},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","page","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/franchises/{id}":{"get":{"operationId":"getFranchise","summary":"Read one franchise","description":"A franchise in another tenant and one that never existed are the same answer: 404.","security":[{"oauth2":["franchises.read"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ULID."}],"responses":{"200":{"description":"One franchise.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Franchise"},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/webhook_endpoints":{"get":{"operationId":"listWebhookEndpoints","summary":"List the endpoints this token’s tenant has subscribed","description":"What an automation platform reconciles against on start-up. The signing secret is not among the fields: it is shown once, at creation.","security":[{"oauth2":["webhooks.write"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."}],"responses":{"200":{"description":"List the endpoints this token’s tenant has subscribed","content":{"application/json":{"schema":{"type":"object","properties":{"data":{},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"subscribeWebhookEndpoint","summary":"Subscribe a URL to this tenant’s events","description":"The REST Hook subscribe every automation platform expects. Returns the endpoint and its signing secret, once. Requires an `Idempotency-Key`.","security":[{"oauth2":["webhooks.write"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"},"description":"Required. A retry with the same key returns the first response verbatim; the same key with a different body is a 409."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"https, and never a private address."},"description":{"type":"string","nullable":true},"events":{"type":"array","items":{"type":"string"},"description":"Empty means every publishable event. An internal event is refused by name."}},"required":["url"]}}}},"responses":{"200":{"description":"Subscribe a URL to this tenant’s events","content":{"application/json":{"schema":{"type":"object","properties":{"data":{},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]}}}},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Idempotency key reused with a different body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/webhook_endpoints/{id}":{"delete":{"operationId":"unsubscribeWebhookEndpoint","summary":"Unsubscribe","description":"The REST Hook unsubscribe. The endpoint is retired rather than erased, so the deliveries already made keep their record.","security":[{"oauth2":["webhooks.write"]}],"parameters":[{"name":"X-HRP-VERSION","in":"header","required":true,"schema":{"type":"string","enum":["2026-10-01"],"default":"2026-10-01"},"description":"Required on every request. There is no default: a client that never states a version breaks silently on the day one moves."},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ULID."}],"responses":{"204":{"description":"Unsubscribed."},"400":{"description":"Malformed request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Not authenticated","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Insufficient scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"API version no longer served","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}}}