{"openapi":"3.1.0","info":{"title":"SupplierHours API","version":"1.0.0","description":"One REST API over supplier facility hours. Suppliers publish operating, receiving, and pickup schedules (write scope); shippers and carriers read live availability (read scope). Write keys can call read endpoints but see only their own locations — aggregator-wide reads require a consumer read key.","contact":{"url":"https://supplierhours.com/docs"},"license":{"name":"Proprietary","url":"https://supplierhours.com"}},"servers":[{"url":"https://supplierhours.com"}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"tags":[{"name":"Read","description":"Consumer endpoints (read scope)."},{"name":"Write","description":"Supplier endpoints (write scope)."}],"paths":{"/api/v1/locations":{"get":{"tags":["Read"],"operationId":"listLocations","summary":"List/search locations","description":"Filterable directory of facilities. With ?near, results sort nearest-first and include distanceMiles. Supplier keys see only their own locations.","parameters":[{"name":"city","in":"query","schema":{"type":"string"},"description":"Case-insensitive city match."},{"name":"region","in":"query","schema":{"type":"string"},"description":"Two-letter state code, e.g. CA."},{"name":"postalCode","in":"query","schema":{"type":"string"},"description":"Exact ZIP match."},{"name":"supplierId","in":"query","schema":{"type":"string","format":"uuid"},"description":"Restrict to one supplier."},{"name":"near","in":"query","schema":{"type":"string","example":"34.5,-117.3"},"description":"'<lat>,<lng>' radius-search center. Adds distanceMiles and sorts nearest-first."},{"name":"radius","in":"query","schema":{"type":"number","default":50,"maximum":500},"description":"Miles from 'near'. Default 50, max 500."}],"responses":{"200":{"description":"Up to 200 matching locations.","content":{"application/json":{"schema":{"type":"object","properties":{"locations":{"type":"array","items":{"$ref":"#/components/schemas/Location"}}},"required":["locations"]}}}},"400":{"description":"Malformed 'near' parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry-After header gives seconds until the window resets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/locations/{id}/hours":{"get":{"tags":["Read"],"operationId":"getHours","summary":"Weekly hours windows","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Location id."},{"name":"type","in":"query","schema":{"$ref":"#/components/schemas/HoursType"},"description":"Filter to one schedule type."}],"responses":{"200":{"description":"Recurring weekly windows for the location.","content":{"application/json":{"schema":{"type":"object","properties":{"locationId":{"type":"string","format":"uuid"},"hours":{"type":"array","items":{"$ref":"#/components/schemas/HoursWindow"}}},"required":["locationId","hours"]}}}},"400":{"description":"Invalid type parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Location not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry-After header gives seconds until the window resets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Write"],"operationId":"replaceHours","summary":"Replace all windows for one schedule type","description":"Idempotent full replacement. The key must belong to the supplier that owns the location. Publishing hours also bumps the location's lastVerifiedAt.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Location id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplaceHoursRequest"}}}},"responses":{"200":{"description":"Windows replaced.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"type":{"$ref":"#/components/schemas/HoursType"},"count":{"type":"integer"}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key does not own this location.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Location not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry-After header gives seconds until the window resets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/locations/{id}/closures":{"get":{"tags":["Read"],"operationId":"listClosures","summary":"Blackout date ranges","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Location id."},{"name":"from","in":"query","schema":{"type":"string","format":"date"},"description":"Only closures ending on/after this date."}],"responses":{"200":{"description":"Closures, ordered by start date.","content":{"application/json":{"schema":{"type":"object","properties":{"locationId":{"type":"string","format":"uuid"},"closures":{"type":"array","items":{"$ref":"#/components/schemas/Closure"}}},"required":["locationId","closures"]}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Location not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry-After header gives seconds until the window resets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Write"],"operationId":"createClosure","summary":"Add a blackout range","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Location id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateClosureRequest"}}}},"responses":{"201":{"description":"Closure created.","content":{"application/json":{"schema":{"type":"object","properties":{"closure":{"$ref":"#/components/schemas/Closure"}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key does not own this location.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Location not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry-After header gives seconds until the window resets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/locations/{id}/availability":{"get":{"tags":["Read"],"operationId":"getAvailability","summary":"Per-day open/closed answer","description":"Weekly hours merged with closures into a day-by-day, per-type answer. Times are facility-local; the response carries the location's IANA timezone.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Location id."},{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date"},"description":"Start date (facility-local), YYYY-MM-DD."},{"name":"days","in":"query","schema":{"type":"integer","minimum":1,"maximum":14,"default":1},"description":"Number of days to compute (max 14)."}],"responses":{"200":{"description":"Availability for each requested day.","content":{"application/json":{"schema":{"type":"object","properties":{"locationId":{"type":"string","format":"uuid"},"timezone":{"type":"string","example":"America/Chicago"},"lastVerifiedAt":{"type":"string","format":"date-time"},"stale":{"type":"boolean","description":"True if not verified in 90 days."},"days":{"type":"array","items":{"$ref":"#/components/schemas/DayAvailability"}}},"required":["locationId","timezone","days"]}}}},"400":{"description":"Invalid date/days parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Location not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry-After header gives seconds until the window resets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/usage":{"get":{"tags":["Read"],"operationId":"getUsage","summary":"Your usage and limits","description":"Self-service visibility for the calling key: tier, current rate-limit window, and 7-day history. Counts against the rate limit like any other call.","responses":{"200":{"description":"Usage for the calling key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry-After header gives seconds until the window resets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Authorization: Bearer dh_your_key"},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"HoursType":{"type":"string","enum":["operating","receiving","pickup"]},"HoursWindow":{"type":"object","description":"A window of 00:00-23:59 means the facility is open 24 hours that day.","properties":{"type":{"$ref":"#/components/schemas/HoursType"},"dayOfWeek":{"type":"integer","minimum":0,"maximum":6,"description":"0 = Sunday .. 6 = Saturday."},"opensAt":{"type":"string","example":"08:00:00","description":"Facility-local time. Responses use HH:MM:SS."},"closesAt":{"type":"string","example":"17:00:00"},"appointmentRequired":{"type":"boolean","description":"True = trucks must book ahead (not FCFS)."}},"required":["type","dayOfWeek","opensAt","closesAt"]},"ReplaceHoursRequest":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/HoursType"},"windows":{"type":"array","maxItems":28,"items":{"type":"object","properties":{"dayOfWeek":{"type":"integer","minimum":0,"maximum":6},"opensAt":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$","example":"08:00","description":"HH:MM, 24h. Must be before closesAt. Send 00:00-23:59 for open-24-hours."},"closesAt":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$","example":"17:00"},"appointmentRequired":{"type":"boolean","default":false,"description":"True = trucks must book ahead (not FCFS)."}},"required":["dayOfWeek","opensAt","closesAt"]}}},"required":["type","windows"]},"Closure":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"oneOf":[{"$ref":"#/components/schemas/HoursType"},{"type":"null"}],"description":"null = entire facility closed."},"startsOn":{"type":"string","format":"date"},"endsOn":{"type":"string","format":"date"},"reason":{"type":["string","null"]}},"required":["id","startsOn","endsOn"]},"CreateClosureRequest":{"type":"object","properties":{"type":{"oneOf":[{"$ref":"#/components/schemas/HoursType"},{"type":"null"}],"description":"Omit or null to close the entire facility."},"startsOn":{"type":"string","format":"date"},"endsOn":{"type":"string","format":"date","description":"Inclusive; must be on/after startsOn."},"reason":{"type":["string","null"],"maxLength":500}},"required":["startsOn","endsOn"]},"TypeAvailability":{"oneOf":[{"type":"object","properties":{"status":{"const":"open"},"windows":{"type":"array","items":{"type":"object","properties":{"opensAt":{"type":"string"},"closesAt":{"type":"string"},"appointmentRequired":{"type":"boolean"}}}}},"required":["status","windows"]},{"type":"object","properties":{"status":{"const":"closed"},"reason":{"type":["string","null"]}},"required":["status"]},{"type":"object","properties":{"status":{"const":"unavailable"}},"required":["status"],"description":"No hours published for this type."}]},"DayAvailability":{"type":"object","properties":{"date":{"type":"string","format":"date"},"dayOfWeek":{"type":"integer","minimum":0,"maximum":6},"operating":{"$ref":"#/components/schemas/TypeAvailability"},"receiving":{"$ref":"#/components/schemas/TypeAvailability"},"pickup":{"$ref":"#/components/schemas/TypeAvailability"}},"required":["date","dayOfWeek","operating","receiving","pickup"]},"Location":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"supplierId":{"type":"string","format":"uuid"},"supplierName":{"type":"string"},"addressLine1":{"type":"string"},"addressLine2":{"type":["string","null"]},"city":{"type":"string"},"region":{"type":"string"},"postalCode":{"type":"string"},"country":{"type":"string","example":"US"},"timezone":{"type":"string","example":"America/Los_Angeles"},"latitude":{"type":["number","null"]},"longitude":{"type":["number","null"]},"publicNotes":{"type":["string","null"],"description":"Freeform driver/carrier instructions from the supplier."},"lastVerifiedAt":{"type":"string","format":"date-time"},"stale":{"type":"boolean","description":"True if not verified in 90 days."},"distanceMiles":{"type":"number","description":"Present only with ?near."}},"required":["id","name","supplierId","city","region","timezone"]},"UsageResponse":{"type":"object","properties":{"keyPrefix":{"type":"string"},"scope":{"type":"string","enum":["read","write"]},"tier":{"type":"string","enum":["trial","standard","enterprise","supplier"]},"currentWindow":{"type":"object","properties":{"limit":{"type":"integer"},"used":{"type":"integer"},"remaining":{"type":"integer"},"resetAt":{"type":"string","format":"date-time"}}},"history":{"type":"array","items":{"type":"object","properties":{"windowStart":{"type":"string","format":"date-time"},"count":{"type":"integer"}}}}}}}}}