The Basecamp API documentation leads with flat routes and a User-Agent rule
API documentation for Basecamp
At a glance
- What is it?
- The basecamp/bc-api repository is the reference for Basecamp's REST API, and two things stand out before you read a single endpoint. Requests go to a specific numbered host rather than basecamp.com, and a flat route form that drops the project from the URL entirely is now the canonical shape, with the older project-scoped form kept working forever.
- Who is it for?
- Adopt the Basecamp API directly if you need to integrate with a Basecamp account and are willing to handle OAuth 2.0 and the documented headers, since the flat routes and geared pagination make the surface predictable. Skip it if you want a client library, because the SDK is a separate project and this repository is documentation, and if your app cannot send a User-Agent, since Basecamp returns 400 without one.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- GitHub does not report a main language for this repository.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Three routes in, and this repository is the third
The README opens by sorting the reader into three groups, and the choice is a good guide to how much you want to build.
AI agents are pointed at the Basecamp Skills or the Basecamp CLI. Integrating with your own app or service is pointed at the Basecamp SDK, which provides full-featured API clients and an OpenAPI spec for code generation. And going fully custom means using the OpenAPI spec and this REST API reference, which cover authentication, pagination and every endpoint in detail.
So this repository is the raw reference layer. It is where you end up when you want to know what a field is called or what a header requires, not where you start if you just want to read a to-do list.
The fact that there is a separate SDK with an OpenAPI spec is worth noting for a specific reason. A machine-readable spec means a client can be generated, and a generated client handles the pagination and header rules for you, which are exactly the parts of this API that are easy to get subtly wrong.
The repository itself is documentation. The top-level entries are a README.md, a sections/ directory holding the per-resource pages, an AGENTS.md, a CONDUCT.md and a .claude/ directory, and there is no source code and no release history, since the API changes in place and the documentation is updated alongside it. The last push was on 2026-10-16.
The base URL is not basecamp.com
The single most important detail in the making-a-request section is easy to miss, and the documentation calls it out explicitly with a note about the different domain.
All URLs start with `https://3.basecampapi.com/999999999/`. The path is prefixed with the account ID, and there is no `/api/v1` API prefix. The URLs are HTTPS only.
So a request for all the projects on your account appends the projects index path, giving something like `https://3.basecampapi.com/999999999/projects.json`, and in cURL it looks like this:
curl -H "Authorization: Bearer $ACCESS_TOKEN" -A 'MyApp ([email protected])' https://3.basecampapi.com/999999999/projects.jsonThe account ID is part of the path rather than a subdomain parameter, which is worth internalising because it means a single account's resources are all under one prefix, and a multi-account integration has to vary that prefix per request rather than changing hosts.
Creating something is the same idea with the JSON headers and body added:
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-A 'User-Agent: MyApp ([email protected])' \
-d '{ "name": "My new project!" }' \
https://3.basecampapi.com/999999999/projects.jsonThe documentation also gives you a way to try the examples without editing them, which is a small thing that makes a big difference when you are learning an API. Copy your OAuth 2.0 access token into your clipboard and run:
export ACCESS_TOKEN=PASTE_ACCESS_TOKEN_HERE
export ACCOUNT_ID=999999999Then you can copy and paste any example from the docs. Every endpoint page includes Copy as cURL examples, and the suggestion is to pipe the output to a JSON pretty printer to make it readable, trying jsonpp or `json_pp` on macOS:
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://3.basecampapi.com/999999999/projects.json | json_ppFor a documentation set this size, being able to run the examples verbatim against a real account is the difference between reading the spec and learning the API.
Flat routes are the canonical form now
This is the part of the reference that has clearly changed recently, and it is the thing a new integration should be built on.
Every resource in the Basecamp API is uniquely identified by its own ID, and you can access any resource directly without including its project in the URL:
GET /todos/67890.json
GET /messages/12345.json
GET /todolists/456/todos.jsonThese flat routes are the canonical form for all new integrations, because the project context is derived server-side from the resource itself, so you rarely need to supply a bucket_id.
For listing and creating resources under a parent, you use the parent's ID directly:
GET /todolists/456/todos.json # list to-dos in a to-do list
POST /todolists/456/todos.json # create a to-do in a to-do list
GET /message_boards/789/messages.json # list messages on a board
POST /recordings/123/comments.json # comment on any recordingThe previous project-scoped form, `/buckets/{project_id}/...`, remains available and will continue to work in perpetuity, but is considered legacy. All the endpoint documentation in the guide uses flat routes as the primary form, with the legacy equivalents noted at the bottom of each section.
That word perpetuity is a commitment, and it is a good one to rely on, because the older shape is the one most existing integrations use and the new one is the one you should write against.
There is one carve-out. A few project-level collection endpoints, specifically message types, the webhooks index and create, and tools, still require the project-scoped form, and those are noted in their respective sections. So the flat form is the default but not universal, which is the sort of exception that is easy to miss if you only read the overview.
The practical effect is that the URL you build is a function of the resource you want, not of the project it happens to live in. For an integration that walks many projects, that removes a layer of URL construction entirely.
Headers are mandatory, and the User-Agent is how they contact you
The authentication section says OAuth 2.0 must be used, and explains why in terms that make the choice obvious. OAuth 2.0 allows users to authorize your application to use Basecamp on their behalf without having to copy and paste API tokens or touch sensitive login information.
Separately from authentication, there is a header rule that catches people out because it is enforced rather than merely recommended. You must include a User-Agent header with both the name of your application and a link to your application or your email address. If you do not include a User-Agent header, you get a 400 Bad Request response.
The examples given of acceptable headers are `User-Agent: Freshbooks (http://freshbooks.com/contact.php)` and `User-Agent: Fabian's Ingenious Integration ([email protected])`.
The stated reason for requiring it is human. The information is used to get in touch if you are doing something wrong, so they can warn you before you are blacklisted, or if you are doing something awesome, so they can congratulate you. That framing is unusual in an API reference and it tells you the header is there for the developer's benefit as much as the platform's.
The JSON rules are equally strict. JSON is used for all API data, with no root element and snake_case for object keys. You have to send the Content-Type header `Content-Type: application/json; charset=utf-8` when you are POSTing or PUTing data, and all API URLs end in .json to indicate that they return JSON, with Accept: application/json as the alternative. If you do not include the Content-Type header you get a 415 Unsupported Media Type response.
So there are three failure modes a client has to handle at the very first request: no User-Agent gives 400, no Content-Type on a write gives 415, and an unauthenticated request fails on the bearer token.
Pagination is geared, and you must not build the URLs yourself
The pagination section has a detail that is unusual enough to be worth reading twice.
Most collection APIs paginate their results and the number of requests per page is variable. In most cases Basecamp uses a geared pagination ratio, with 15 results on page 1, 30 on page 2, 50 on page 3, and then 100 on page 4 and above. The idea behind geared pagination is that early pages are small so the first response is fast, and later pages are large so the total request count stays low.
The Basecamp API follows the RFC5988 convention of using the Link header to provide URLs for the next page, and the instruction is explicit: follow this convention to retrieve the next page of data, and please don't build the pagination URLs yourself.
The reason is that the geared ratio means page sizes are not a fixed multiple of anything, so a client that assumes a page size and computes offsets will skip or repeat records. The example response header for the third page of messages is:
Link: <https://3.basecampapi.com/999999999/message_boards/3/messages.json?page=4>; rel="next"If the Link header is blank, that is the last page. The API also provides an X-Total-Count header, which displays the total number of resources in the collection you are fetching, which is useful for progress indicators.
The caching rules are compulsory too. You must use HTTP freshness headers to speed up your application and lighten the load on the servers. Most API responses include an ETag or a Last-Modified header, and you store those on the first request and send them back as If-None-Match and If-Modified-Since on subsequent requests. If the resource has not changed you get a 304 Not Modified with no body.
So a well-behaved Basecamp client stores validators and honours them. The server treats that as a requirement rather than a courtesy, which means a client that ignores caching is not just slower but is behaving outside the contract.
Errors are expected, and the retry guidance is explicit
The handling of errors section is short and it is a specification rather than advice.
API clients must expect and gracefully handle transient server errors and rate limits, and the recommendation is to bake graceful 5xx and 429 retries into your integration from the beginning so errors are handled automatically.
Rate limiting is the specific case documented. A 429 Too Many Requests response is returned when you have exceeded a rate limit, and you consult the Retry-After response header to determine how long to wait, in seconds, before retrying.
Retry-After is the part worth taking seriously. A client with an exponential backoff of its own is guessing, and a client that reads Retry-After is following the instruction. Since the API is shared across many integrations, the server knows when it has capacity again and telling you is better for everyone than making you probe for it.
That is also the theme of the whole reference, read end to end. The User-Agent header exists so they can warn you before you are blacklisted. The caching requirement exists to lighten the load on the servers. The rate limit response tells you how long to wait. And the pagination instruction exists so that clients do not guess at page sizes the server has already decided.
For a documentation set, that is a coherent philosophy. The API is opinionated about how you should treat it, and it says why in each case rather than only listing the requirement.
The one thing this repository does not give you is a client, and that is deliberate. The SDK with its generated clients and the OpenAPI spec exist for that, and the CLI and the skills exist for people who want to drive Basecamp from a terminal or let an agent do it. What lives here is the part that has to be understood rather than generated, and it is enough to write a client from if you want to.
Editorial conclusion
Adopt the Basecamp API directly if you need to integrate with a Basecamp account and are willing to handle OAuth 2.0 and the documented headers, since the flat routes and geared pagination make the surface predictable. Skip it if you want a client library, because the SDK is a separate project and this repository is documentation, and if your app cannot send a User-Agent, since Basecamp returns 400 without one. Verify that you use the flat route form, which is canonical for new work while the project-scoped form remains supported, that you send the User-Agent with both the application name and a contact link, and that your pagination follows the Link header rather than constructing page URLs.
Frequently asked questions
What is the Basecamp API base URL and how do I authenticate?
All URLs start with `https://3.basecampapi.com/999999999/`, with the account ID in the path and no API version prefix. You must authenticate with OAuth 2.0, sending the token as a bearer in the Authorization header, so a request looks like `curl -H "Authorization: Bearer $ACCESS_TOKEN" -A 'MyApp ([email protected])' https://3.basecampapi.com/999999999/projects.json`.
What is the difference between flat routes and project-scoped routes?
Flat routes address a resource directly by its own ID, such as `GET /todos/67890.json`, and the project context is derived server-side, so you rarely need a bucket_id. They are the canonical form for new integrations. The older `/buckets/{project_id}/...` form remains available and will continue to work in perpetuity but is considered legacy, with a few project-level endpoints still requiring it.
How does Basecamp API pagination work and what should I do about caching and rate limits?
Most collections paginate with a geared ratio of 15, 30, 50 and then 100 results per page, and the API follows RFC5988 by giving next-page URLs in the Link header, so you should follow those rather than building page URLs yourself. You must send back ETag and Last-Modified as If-None-Match and If-Modified-Since to get 304 responses. Clients should retry 5xx and 429 gracefully, waiting for the time given in the Retry-After header.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/basecamp-bc-api)Was this useful?