Product that suits modern B2B Tech companies

Book Demo
B
Book demo call-to-action illustration
BACK
B

CharlieHR API: authentication, pagination and rate limits

Technical Guides
October 30, 2026
Summarise the blog with AI
Open in ChatGPT
Ask questions about this page
Open in Claude
Ask questions about this page

Last verified: 2026-10-30

This page is about how to call the CharlieHR API: the credential, the paging loop, pacing and change detection. For what each endpoint returns, field by field, see our companion post, What data the CharlieHR API exposes.

Key takeaways

  • Every request carries one static Authorization header holding the Client ID and Client Secret. There is no token exchange to build.
  • Only a Super Admin can generate keys. CharlieHR shows the secret once, then masks it.
  • Every data endpoint is a GET. The only writes described are creating notes and note types.
  • List endpoints cap per_page at 25. End the loop on the meta block's total_pages, and keep an empty page as the backstop.
  • CharlieHR publishes no rate limit and no throttle response. Pace conservatively and handle 429 as RFC 6585 defines it.
  • CharlieHR's integration help describes no webhooks, so change detection is a scheduled re-read.

CharlieHR hands you a Client ID and a Client Secret, which suggests an OAuth token exchange. No published source describes one. The pair is the credential itself.

Assume OAuth and you build a refresh path with no endpoint to call. Assume any admin can issue keys and onboarding stalls. Assume a short page is the last page and you stop after 25 records.

CharlieHR's v1 API is REST returning JSON at https://charliehr.com/api/v1, with the reference at charliehr.com/api_docs. Each call sends Authorization: Token token=<CLIENT_ID>:<CLIENT_SECRET>, list endpoints take page and per_page, and CharlieHR publishes no numeric request limit. This page is written for engineers building a direct CharlieHR connection for their customers.

Credentials and the Authorization header

A Super Admin generates the Client ID and Client Secret, and you send both in one header on every request. CharlieHR's help center says the keys are unique to the customer's company and only Super Admins can generate them, so name that role when you ask. The path:

  1. A Super Admin opens Company, Integrations, API Access, Your API Keys and selects Generate.
  2. CharlieHR shows a Client ID and a Client Secret. The secret appears once, then it is masked.
  3. Copy the secret at that moment. Pasting the masked value into an integration returns an error.
  4. The Super Admin passes the keys to the other application. The help article gives no request format for this step.

Step 2 is where onboarding breaks. Only the copy saved at generation works, so a lost secret means going back to the Super Admin to generate a new pair.

The request:

GET /api/v1/company HTTP/1.1
Host: charliehr.com
Authorization: Token token=:

Both halves go in one value, joined by a colon. The scheme is Token, which is neither Bearer nor Basic, so a client library's Bearer setting won't produce it.

Is this OAuth client credentials? No. No source describes a token endpoint, grant type, scope or access-token expiry. The pair is a static credential, with nothing to fetch or refresh, and the header is the whole flow.

With a header that works, the question becomes what it reaches.

Endpoint surface

Every data endpoint is a GET. The only writes described are creating notes and note types on a team member, so confirm against the reference before you scope anything else as a write.

ResourcePathsScoping
Company/companyNo id in the path
Team members/team_members, /team_members/:idList or by id
Notes/team_members/:id/notes, /team_member_note_typesNested under a member; notes and note types are the only writes
Offices and teams/offices, /teamsList
Leave requests/leave_requests, /leave_requests/:id, /team_members/:id/leave_requestsCompany-wide or per member; date filters on /leave_requests
Leave allowances/leave_allowances, /team_members/:id/leave_allowanceCompany-wide or per member
Salaries/salaries, /team_members/:id/salariesCompany-wide or per member
Bank accounts/bank_accounts, /bank_accounts/:team_member_idList, or by team member id

The API also covers holidays, restricted dates, working locations and working weeks. The companion post lists every path and what each one returns, including how to decide which salary is current.

The base URL is the production host, and testing needs a CharlieHR company account you control, so ask CharlieHR about test access. Salaries and bank accounts carry pay and bank details. Request them only if your product needs them.

Each list above comes back in pages of at most 25.

Pagination, filters and change detection

List endpoints take page and per_page, and per_page is capped at 25. Ask for 100 and you still get 25. A loop that stops when a page comes back shorter than what it asked for will stop after the first page, without an error.

Each list response carries a meta block: total_results, results_per_page, current_page, total_pages, prev_url and next_url. Use it to decide when to stop.

A loop that holds up:

  1. Send page=1 with per_page=25, and keep the page size configurable.
  2. Key every record on its id, so a repeated page cannot create duplicates.
  3. Increment page after each response, however many records came back.
  4. Stop when current_page equals total_pages. Also stop on an empty page, as a backstop if meta is ever missing.

The backstop costs at most one extra request per list. If CharlieHR changes the cap, the configurable page size makes it a config change.

For the mechanics behind this kind of loop, see Bindbee's guide to offset pagination.

/leave_requests takes start_date and end_date. CharlieHR doesn't state the date format these accept. An empty filtered response could mean there was no leave, or that the API read your date differently. Run the filter once over a window where you know leave exists before trusting an empty result.

For change detection, the Integrations & API collection in CharlieHR's help center covers key generation, Slack, Xero, Teamtailor and Workable. None of its articles describes outbound webhooks. Plan on scheduled re-reads at a cadence your product tolerates, and compare records by id against what you stored.

Re-reading every list on a schedule means more traffic, all of it against a limit nobody has published.

Rate limits and throttling

CharlieHR publishes no numeric limit and documents no throttle response: no status code, no Retry-After and no quota headers. Build against the standard instead. RFC 6585 (April 2012) defines 429 Too Many Requests as the client sending too many requests in a given amount of time, and lets the response include Retry-After. It does not say how a server counts requests or identifies the client, so it can't tell you CharlieHR's window or unit. Get those from CharlieHR, or learn them from your own traffic.

The pacing below is our recommendation. It holds without a published limit:

  • Start with one request in flight per key.
  • Ramp concurrency in small steps, and hold at the last level that ran clean.
  • Treat a 429 as the ceiling, and drop below the rate that caused it.
  • Honor Retry-After when present. When it isn't, back off exponentially with jitter.
  • Log every non-2xx response with its headers, since CharlieHR's actual throttle signal is undocumented.

Bindbee's BambooHR rate-limits guide shows how another HRIS that publishes no numeric limit signals throttling. Before committing an estimate, ask CharlieHR for:

  • The numeric limit and its window
  • The throttled response, with its status code and headers
  • The accepted date format for start_date and end_date
  • Any write endpoints beyond notes and note types, and a test environment

That list, plus secret custody and scheduled re-reads, is what you own alone when you build direct.

Building direct or through a unified API

Built direct, you own a Super Admin's secret, a pacing scheme for an unpublished limit, and polling in place of push. A unified API puts a managed layer between your product and CharlieHR. That changes who holds the credential, which limit you design against, and how you hear about changes.

Bindbee is one example, and it connects to CharlieHR through its API. Through Bindbee's CharlieHR integration, you can read CharlieHR employment, compensation, bank info, company, group, time off and time-off balance data. The Model Availability Matrix shows per-model support for every integration.

ConcernBuilding direct to CharlieHRThrough Bindbee
Credential custodyYour product receives and stores the Client SecretThe customer's Super Admin still generates the Client ID and Secret, then enters them in Bindbee's hosted Magic Link or Embedded SDK. The keys never pass through your product
Rate limitsNo published CharlieHR limit, so you pace conservativelyBindbee's API allows 200 requests per minute per connector, so one customer's traffic can't exhaust another's. That covers your calls to Bindbee, not Bindbee's calls to CharlieHR
Change detectionScheduled re-reads, since no webhooks are describedWebhooks notify you when a connector links, syncs, fails or needs relinking, and when a sync creates or updates records
Pay and bank dataEvery key reads everything the API exposesScoping switches models and fields off, so bank details you don't need never sync, and the customer's consent screen lists only what you kept
Endpoints outside the modelYou call whatever the reference documentsPassthrough forwards raw requests to CharlieHR's own API using the connector's existing credentials

Bindbee is SOC 2 Type II, HIPAA, GDPR and ISO 27001 compliant, which matters here because CharlieHR's salary and bank data passes through it.

The trade is freshness. A direct read returns what CharlieHR holds now. A Bindbee Production connector syncs every 24 hours by default, adjustable per connection on request, and its webhooks fire when a sync runs, so a change in CharlieHR reaches you on the next sync. If your product needs same-hour data, that favors building direct. If you want to stop owning the unknowns above, it favors the layer.

To check CharlieHR coverage for your use case, see Bindbee's CharlieHR integration page or book a demo.

FAQ

Does the CharlieHR API use OAuth?

No. Every request sends a static header, Authorization: Token token=<CLIENT_ID>:<CLIENT_SECRET>, using keys a Super Admin generates in CharlieHR. There is no token endpoint, scope or expiry to manage.

What is the CharlieHR API page size limit?

List endpoints cap per_page at 25. Each response's meta block carries current_page and total_pages, so stop when the two match rather than when a page comes back short.

Does CharlieHR have webhooks?

CharlieHR's Integrations & API help collection describes none. Detect changes with scheduled re-reads, comparing records by id against your stored copy.

Kunal Tyagi
CTO
Bindbee
VIEW AUTHOR
BLOG_

Related blogs