{"openapi":"3.1.0","info":{"title":"TenderBoard API","version":"1.0.0","description":"Public tender data for Uganda, plus the member endpoints the TenderBoard apps use. Amounts are UGX integers; every timestamp is UTC in ISO 8601.","contact":{"email":""}},"servers":[{"url":"https:\/\/www.tenderboard.buildal.net\/api\/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Tenders","description":"Notices and their taxonomies"},{"name":"Auth","description":"Device tokens for the apps"},{"name":"Awards","description":"Who won which contract, and what the losing bids were"},{"name":"Me","description":"The signed-in member, scoped to their team"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A personal access token. Device tokens carry tenders:read, me:read and me:write; partner keys are usually tenders:read only."}},"schemas":{"Tender":{"type":"object","properties":{"id":{"type":"integer"},"slug":{"type":"string"},"title":{"type":"string"},"reference":{"type":["string","null"]},"buyer":{"type":"string"},"sector":{"type":["string","null"]},"district":{"type":["string","null"]},"category":{"type":"string"},"noticeType":{"type":"string"},"closesAt":{"type":"string","format":"date-time"},"publishedAt":{"type":"string","format":"date-time"},"valueBand":{"type":["string","null"]},"isFeatured":{"type":"boolean"}}},"TenderDetail":{"allOf":[{"$ref":"#\/components\/schemas\/Tender"},{"type":"object","properties":{"summary":{"type":["string","null"]},"excerpt":{"type":["string","null"]},"access":{"type":"object","properties":{"locked":{"type":"boolean"}}},"body":{"type":["string","null"],"description":"Gated: present only when unlocked"},"contact":{"type":["string","null"],"description":"Gated"},"estimatedValue":{"type":["integer","null"],"description":"Gated. UGX"},"bidFee":{"type":["integer","null"],"description":"Gated. UGX"},"sourceUrl":{"type":["string","null"],"description":"Gated"}}}]},"Award":{"type":"object","properties":{"slug":{"type":"string"},"subject":{"type":"string"},"reference":{"type":["string","null"]},"provider":{"type":"string"},"amountUgx":{"type":["integer","null"]},"buyer":{"type":"string"},"buyerSlug":{"type":"string"},"publishedAt":{"type":["string","null"],"format":"date-time"}}},"AwardDetail":{"allOf":[{"$ref":"#\/components\/schemas\/Award"},{"type":"object","properties":{"method":{"type":["string","null"]},"vatInclusive":{"type":"boolean"},"tenderSlug":{"type":["string","null"]},"sourceUrl":{"type":["string","null"]},"marginPercent":{"type":["integer","null"],"description":"How far below the next bid the winner came in"},"bidders":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"amount":{"type":["integer","null"]},"isWinner":{"type":"boolean"},"reason":{"type":["string","null"]}}}}}}]},"Taxonomy":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"},"openCount":{"type":"integer"}}},"Plan":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"interval":{"type":"string","enum":["one_off","month","year"]},"priceUgx":{"type":"integer"},"seats":{"type":"integer"},"unlockCount":{"type":["integer","null"]},"features":{"type":"array","items":{"type":"string"}}}},"Token":{"type":"object","properties":{"token":{"type":"string"},"abilities":{"type":"array","items":{"type":"string"}},"user":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"email":{"type":"string"}}}}},"Me":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"email":{"type":"string"},"teamId":{"type":"integer"},"subscription":{"type":"object","properties":{"state":{"type":"string","enum":["none","active","grace","expired"]},"endsAt":{"type":["string","null"],"format":"date-time"}}},"unlocks":{"type":"object","properties":{"free":{"type":"integer"},"pack":{"type":"integer"}}}}},"Watching":{"type":"object","properties":{"watching":{"type":"boolean"}}},"Alert":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"frequency":{"type":"string","enum":["daily","weekly","instant"]},"filters":{"type":"object"},"channels":{"type":"array","items":{"type":"string"}},"lastSentAt":{"type":["string","null"],"format":"date-time"}}},"Unlock":{"type":"object","properties":{"id":{"type":"integer"},"source":{"type":"string","enum":["free","pack","purchase","grant","subscription"]},"grantedAt":{"type":"string","format":"date-time"},"tender":{"$ref":"#\/components\/schemas\/Tender"}}}}},"paths":{"\/tenders":{"get":{"tags":["Tenders"],"summary":"List tenders","description":"Open notices by default. Gated fields are absent unless the caller is entitled to them.","responses":{"200":{"description":"A page of results","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Tender"}},"meta":{"type":"object","description":"Pagination, when the endpoint paginates"},"links":{"type":"object"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."}},"parameters":[{"name":"q","in":"query","required":false,"description":"Full-text search across title, reference and buyer","schema":{"type":"string"}},{"name":"sector","in":"query","required":false,"description":"Sector slug","schema":{"type":"string"}},{"name":"district","in":"query","required":false,"description":"District slug","schema":{"type":"string"}},{"name":"region","in":"query","required":false,"description":"Region slug","schema":{"type":"string"}},{"name":"entity","in":"query","required":false,"description":"Buyer slug","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"description":"supplies, services, works or consultancy","schema":{"type":"string"}},{"name":"notice_type","in":"query","required":false,"description":"Notice type slug","schema":{"type":"string"}},{"name":"closing","in":"query","required":false,"description":"today, week or month","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"open (default), closed or all","schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"description":"closing, newest or value","schema":{"type":"string"}},{"name":"per_page","in":"query","required":false,"description":"1 to 100, default 20","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number","schema":{"type":"integer"}}],"security":[]}},"\/tenders\/{slug}":{"get":{"tags":["Tenders"],"summary":"Read one tender","description":"The full notice. `access.locked` is true when the caller has not unlocked it; the gated fields are then simply not in the payload.","responses":{"200":{"description":"The record","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/TenderDetail"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."}},"parameters":[{"name":"slug","in":"path","required":true,"description":"The tender slug","schema":{"type":"string"}}],"security":[]}},"\/sectors":{"get":{"tags":["Tenders"],"summary":"List sectors","description":"With live open counts.","responses":{"200":{"description":"A page of results","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Taxonomy"}},"meta":{"type":"object","description":"Pagination, when the endpoint paginates"},"links":{"type":"object"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."}},"security":[]}},"\/districts":{"get":{"tags":["Tenders"],"summary":"List districts","description":"All 136 districts with their region and open counts.","responses":{"200":{"description":"A page of results","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Taxonomy"}},"meta":{"type":"object","description":"Pagination, when the endpoint paginates"},"links":{"type":"object"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."}},"security":[]}},"\/entities":{"get":{"tags":["Tenders"],"summary":"List buyers","description":"Procuring entities with open counts.","responses":{"200":{"description":"A page of results","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Taxonomy"}},"meta":{"type":"object","description":"Pagination, when the endpoint paginates"},"links":{"type":"object"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."}},"security":[]}},"\/awards":{"get":{"tags":["Awards"],"summary":"List contract awards","description":"Best Evaluated Bidder notices: who won, for how much. Free, like the pages.","responses":{"200":{"description":"A page of results","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Award"}},"meta":{"type":"object","description":"Pagination, when the endpoint paginates"},"links":{"type":"object"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."}},"parameters":[{"name":"entity","in":"query","required":false,"description":"Buyer slug","schema":{"type":"string"}},{"name":"provider","in":"query","required":false,"description":"Winning supplier slug","schema":{"type":"string"}},{"name":"per_page","in":"query","required":false,"description":"1 to 100, default 20","schema":{"type":"integer"}}],"security":[]}},"\/awards\/{slug}":{"get":{"tags":["Awards"],"summary":"Read one award","description":"The full notice, including every losing bid and why it was dropped.","responses":{"200":{"description":"The record","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/AwardDetail"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."}},"parameters":[{"name":"slug","in":"path","required":true,"description":"The award slug","schema":{"type":"string"}}],"security":[]}},"\/plans":{"get":{"tags":["Tenders"],"summary":"List plans","description":"Live prices in UGX. Never cache these client-side for longer than a day.","responses":{"200":{"description":"A page of results","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Plan"}},"meta":{"type":"object","description":"Pagination, when the endpoint paginates"},"links":{"type":"object"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."}},"security":[]}},"\/auth\/token":{"post":{"tags":["Auth"],"summary":"Issue a device token","description":"Trades an email and password for a personal access token. Five attempts a minute.","responses":{"200":{"description":"The record","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Token"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."}},"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["email","password","device_name"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","format":"password"},"device_name":{"type":"string","maxLength":120}}}}}},"security":[]},"delete":{"tags":["Auth"],"summary":"Revoke the calling token","description":"Signs this one device out; other devices keep working.","responses":{"204":{"description":"Revoked"},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."},"401":{"description":"No valid token"},"403":{"description":"The token lacks the required ability"}}}},"\/me":{"get":{"tags":["Me"],"summary":"The signed-in member","description":"Identity plus subscription state and unlocks left.","responses":{"200":{"description":"The record","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Me"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."},"401":{"description":"No valid token"},"403":{"description":"The token lacks the required ability"}}}},"\/me\/watchlist":{"get":{"tags":["Me"],"summary":"The team watchlist","description":"Open items first, then closed, both by deadline.","responses":{"200":{"description":"A page of results","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Tender"}},"meta":{"type":"object","description":"Pagination, when the endpoint paginates"},"links":{"type":"object"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."},"401":{"description":"No valid token"},"403":{"description":"The token lacks the required ability"}}}},"\/me\/watchlist\/{tender}":{"post":{"tags":["Me"],"summary":"Watch a tender","description":"Idempotent. Needs the me:write ability.","responses":{"200":{"description":"The record","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Watching"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."},"401":{"description":"No valid token"},"403":{"description":"The token lacks the required ability"}},"parameters":[{"name":"tender","in":"path","required":true,"description":"Tender id","schema":{"type":"integer"}}]},"delete":{"tags":["Me"],"summary":"Stop watching a tender","description":"Idempotent. Needs the me:write ability.","responses":{"200":{"description":"The record","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Watching"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."},"401":{"description":"No valid token"},"403":{"description":"The token lacks the required ability"}},"parameters":[{"name":"tender","in":"path","required":true,"description":"Tender id","schema":{"type":"integer"}}]}},"\/me\/alerts":{"get":{"tags":["Me"],"summary":"The team alerts","description":"Saved searches with their delivery channels.","responses":{"200":{"description":"A page of results","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Alert"}},"meta":{"type":"object","description":"Pagination, when the endpoint paginates"},"links":{"type":"object"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."},"401":{"description":"No valid token"},"403":{"description":"The token lacks the required ability"}}}},"\/me\/unlocks":{"get":{"tags":["Me"],"summary":"What the team has unlocked","description":"Newest first.","responses":{"200":{"description":"A page of results","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Unlock"}},"meta":{"type":"object","description":"Pagination, when the endpoint paginates"},"links":{"type":"object"}}}}}},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."},"401":{"description":"No valid token"},"403":{"description":"The token lacks the required ability"}}}},"\/openapi.json":{"get":{"tags":["Tenders"],"summary":"This document","description":"The machine-readable contract.","responses":{"200":{"description":"The OpenAPI document"},"429":{"description":"Too many requests. 60 a minute anonymous, 300 signed in."}},"security":[]}}}}