OAuth provisioned before code
API credentials requested from Bullhorn by your administrator, authorised against the endpoints your cluster actually uses, and scoped so reporting reads your instance rather than writing to it.
Bullhorn ATS & CRM to Power BI, done for you
OAuth extraction from the Bullhorn REST API, a modelled star schema over job orders, submissions, candidates and placements, and DAX for time-to-fill, submittals-to-hire and recruiter margin — built by a senior US-based engineer, not exported to a spreadsheet every Monday.
Representative dashboard — sample data
Where these projects actually stall
Bullhorn holds the whole recruiting lifecycle — the job order, every submission against it, the interviews, the placement, the rates — and hands almost none of it over in a shape a report can use. The REST API is paged, session-based, and fronted by an OAuth handshake that has to be provisioned before anyone writes a query. Related records arrive as nested references rather than clean foreign keys. That is where most in-house attempts run aground, long before anyone argues about chart colours.
So the work runs extraction, then modelling, then measures. Authenticate properly and page the history once. Flatten job orders, submissions, candidates, client corporations and placements into conformed dimensions and facts. Add the date table and the recruiter-to-desk mapping the ATS does not give you. Then write the DAX that turns dated events into time-to-fill, submittal ratios and gross profit per desk — numbers you would put in front of an owner without a caveat.
What we build from Bullhorn
Buy a block of prepaid Flex hours and, if you're unhappy within the first 4 hours, we refund you in full — no questions asked. Hours are deducted only for work you've reviewed and approved, there's no project minimum, and your banked hours never expire.
What the build covers
Every one sits upstream of the report page, and every one is where an in-house attempt tends to stop.
API credentials requested from Bullhorn by your administrator, authorised against the endpoints your cluster actually uses, and scoped so reporting reads your instance rather than writing to it.
REST sessions expire and result sets are paged. History is pulled once in staged batches, then only records whose modified stamp has moved — the difference between a refresh that finishes and one that times out.
Candidate, client corporation, owner and job order arrive embedded inside each record. They become surrogate keys against real dimensions, so a client means one thing across submissions, placements and invoices.
Open-to-start, open-to-offer, submit-to-interview: each is a different question. We agree the start and end events with you, then write them as separate measures rather than one ambiguous average.
Bill rate minus pay rate is the starting point, not the answer. Burden, overtime and unbilled hours from payroll or timekeeping turn spread into gross profit a finance lead will sign off on.
Scheduled or incremental refresh, row-level security by desk, branch or division, and failures fixed by us out of your banked hours rather than landing on whoever built it.
Your administrator requests API credentials from Bullhorn — a client ID and client secret tied to an API user on your instance. We then run the standard OAuth 2.0 authorisation-code flow against the endpoints that serve your cluster, exchange the code for an access token and a refresh token, and open a REST session that returns the base URL your data actually lives behind. Bullhorn runs several data centres, so hard-coding a URL you found in a forum post is the first way this breaks.
Two things matter beyond getting a 200 back. First, scope: a reporting credential has no business being able to edit a candidate record, so we ask for read access and nothing more. Second, ownership: the credentials belong to your instance and are provisioned by your administrator, so nothing about the connection depends on us continuing to exist. If you later take the model in-house, the access is already yours. Where an agency already replicates Bullhorn into a SQL database or warehouse, we model from that copy instead — usually faster to refresh and easier to join to payroll.
Fewer than people expect. Almost every question a staffing owner asks — fill rate, time-to-fill, submittal ratios, gross profit by desk — resolves to some combination of job orders, submissions, placements, candidates and the client records they hang off. The table below maps each entity to the job it does once the data is modelled rather than listed.
| Entity | What it holds | Role in the model |
|---|---|---|
JobOrder |
Requisition detail, openings, status, employment type, client and owner references, dated open and close events | Fact table for demand — the denominator behind fill rate and time-to-fill |
JobSubmission |
Each candidate put forward against a job order, with status and the dates the status moved | Fact table for recruiter activity and every submittal-to-hire ratio |
Placement |
The filled assignment: pay rate, bill rate, start and end dates, employment type, fee where permanent | Fact table for revenue, spread and contractor headcount over time |
Candidate |
Candidate records, source, skills, status and owner | Dimension for talent supply, redeployment and source-of-hire analysis |
ClientCorporation |
Client accounts, status, industry and billing structure | Client dimension — the key every fact table conforms to |
ClientContact |
Hiring managers and their relationship to the account | Dimension for contact-level activity and account penetration |
CorporateUser |
Recruiters, account managers, teams and departments | Desk dimension, joined to the recruiter-to-branch mapping you supply |
Note and Appointment |
Logged calls, meetings, interviews and other dated activity | Activity fact behind leading indicators rather than lagging results |
Two structural details cause most of the rework. Related records — candidate, client corporation, owner, job order — arrive nested inside each result rather than as flat foreign keys, so they must be expanded and conformed into a star schema before any relationship exists. And most agencies have repurposed the numbered custom fields on job orders and placements to carry something load-bearing: a division, a margin category, a client-specific rate code. Those need documenting in scoping, because nothing in the API tells you that customText7 means "vertical".
In DAX, over a star schema, against a date table that covers the full history. The measures below are the ones that get argued about in every staffing engagement, because each one embeds a definition your leadership has to agree with before the number means anything at all. We write them explicitly and document them, rather than hiding the logic inside a query step.
| Measure | How it is defined | Why it is written this way |
|---|---|---|
| Time to Fill | Average days from job order open date to the start date of the placed candidate | Measured against placements, so cancelled and expired requisitions cannot flatter it |
| Time to Submit | Average days from job order open to the first submission against it | Separates a sourcing problem from a client decision problem — different fixes entirely |
| Submittals to Hire | Submissions divided by placements, over the same period and desk | Ratio, not a count, so a busy recruiter and an effective one stop looking identical |
| Fill Rate | Job orders filled divided by job orders opened in the period | Cohorted by open date, so a slow fill lands in the month the work was taken on |
| Spread | Bill rate less pay rate on contract placements, weighted by hours | Weighted, because an average of rates ignores how many hours each assignment ran |
| Gross Profit | Spread less employment burden and unbilled hours, plus permanent placement fees | Burden comes from payroll, so margin reflects cost rather than the rate on the record |
| Active Assignments | Count of placements whose start and end dates span the selected date | Point-in-time headcount and the ending-soon list that drives redeployment |
Submittals-to-hire by desk is usually the measure that changes a conversation. It is arithmetically trivial and organisationally uncomfortable: it distinguishes the recruiter submitting twelve candidates to make one placement from the one submitting three, and it does it per desk rather than as an agency average that hides both. The modelling discipline underneath — conformed dimensions, one date table, measures instead of calculated columns — is the same craft described on our Power BI data modeling and DAX page.
Bullhorn ships perfectly reasonable reporting, and Canvas goes further for agencies that license it. Both are built to answer questions inside the ATS, against the ATS's own shape of the data. The questions agencies bring us are almost always the ones that cross a boundary — into payroll, into accounting, or across several years of restructured desks.
| What you are trying to do | Reporting inside the ATS | Custom Power BI model |
|---|---|---|
| Daily desk and pipeline lists | Its core strength — live, in context, already built | Possible, but not the reason to build a model |
| Gross profit including burden | Burden and unbilled hours sit in payroll, outside the ATS | Payroll joined to placements and costed hour by hour |
| Time-to-fill your way | Fixed to the start and end events the report author chose | Each definition an explicit, documented measure you can change |
| Multi-year trend across reorganised desks | Constrained by current team structure | Slowly changing dimension keeps history comparable |
| Client concentration and AR exposure | Needs invoice and cash data the ATS does not hold | Accounting modelled alongside placements as a second fact |
| Where the logic lives | Inside the vendor's reporting layer | In a model file in your own tenant, which you own outright |
The practical answer is usually both. Keep the ATS views for running the desk, and build the model for the numbers that decide pricing, headcount and which accounts to defend. If what you want is the wider agency metric set rather than the Bullhorn plumbing, start with Power BI dashboards for staffing agencies instead — that page covers fill rate, spread and redeployment at an industry level, while this one is deliberately the technical half about extracting and modelling one source system.
The model is published to your Power BI workspace and refreshed on a schedule, following Microsoft's documented data refresh guidance (opens in new tab). For agencies with several years of submission and placement history, we normally configure incremental refresh (opens in new tab) so only a recent window is re-pulled each cycle rather than the entire history every morning.
One API-specific trap is worth naming. Paging a REST source means constructing request URLs in code, and the Power BI service refuses to refresh a query whose data source it cannot determine in advance. The fix is to keep the base address constant and vary only the relative path and query parameters. Desk-level visibility is then handled with row-level security (opens in new tab) driven from a mapping table of users to desks and branches, so onboarding a recruiter is a data change rather than a development task.
Prepaid Flex hours that never expire: Starter 20 hours at $125/hr ($2,500), Growth 40 hours at $115/hr ($4,600), Power 80 hours at $100/hr ($8,000), with hourly work from $100/hour on the largest block. A first build covering job orders, submissions, placements and desk-level activity from a single Bullhorn instance usually fits a Starter block. Full gross-profit reporting — payroll burden, timekeeping hours, accounting reconciliation — typically needs Growth, because agreeing the burden rules takes discussion as well as development.
Most tasks ship within 3–5 business days of scope approval. You get a scope and hours estimate before anything starts, and hours deduct only for work you approve. Our guide to Power BI dashboard development cost covers what moves the number, and how prepaid Flex hours work explains the model in full.
Staffing and recruiting agencies running Bullhorn with enough history to trend and no in-house data engineer — typically the point where someone exports to Excel every month and nobody quite trusts the result. Owners, operations leaders and finance managers are the people who usually get in touch, often after a quarter-end where two reports disagreed.
Agencies billing projects rather than placements should read Power BI for professional services, and if your commercial pipeline runs in a separate CRM, the Salesforce Power BI dashboard build covers modelling that alongside your ATS. Contact is form-only — send your request and we'll reply with a scope estimate.
business days, typical
hours, never expire
own everything
A first Bullhorn build usually fits inside one Flex block, and hours deduct only for work you approve. See Flex pricing →
How it works
Your administrator requests read-only API credentials from Bullhorn; we confirm which custom fields carry meaning, agree the time-to-fill and margin definitions, and reply with a scope and hours estimate before any work begins.
A senior US-based engineer pages the history, flattens the nested references into conformed dimensions, builds the star schema and date table, and writes the DAX for time-to-fill, submittal ratios and gross profit — validating totals against your own ATS reports.
Published to your workspace with scheduled or incremental refresh, row-level security so each desk or branch sees only its slice, and documentation you keep. We maintain it from your banked hours.
Representative perspective
Submittals-to-hire per desk ended an argument we'd been having for two years. Two of our busiest recruiters were the least efficient once the ratio was on the screen.Managing director, contract staffing agency
Representative perspective
Our own extract worked in Desktop and died on the first scheduled refresh. Getting the authentication and the paging done properly was most of the value.Operations lead, healthcare recruiting firm
Tell us how you define time-to-fill, what payroll data you hold, and the reporting your leadership actually asks for. We'll confirm the access route, scope the task, and tell you the hours before anything starts — no project minimums, no obligation.
FAQ
Through the Bullhorn REST API using OAuth 2.0. Your administrator requests API credentials from Bullhorn, we authorise against your cluster's endpoints, exchange the authorisation code for tokens, and open a REST session that returns the base URL your instance actually lives on. If your agency already replicates Bullhorn into a database, we can model from that copy instead.
Use them for the daily desk view. A separate model earns its place when you need several years of comparable trend, metrics defined the way your leadership defines them, or Bullhorn data joined to payroll, timekeeping and accounting so recruiter margin reflects burden and real cost rather than the spread on the placement record.
From dated events on the job order and placement records, using whichever start and end points your agency agrees on. Most firms measure from the date a job order opens to the date the placed candidate starts, and separately track time-to-submit and time-to-offer. We write each definition as an explicit DAX measure rather than burying it in a query.
Yes. Row-level security scopes a report so a recruiter sees their own desk, a manager sees their branch or division, and leadership sees everything. We drive it from a mapping table of users to desks, so adding a recruiter is a data change rather than a redevelopment task and no one receives a separate file.
No. We are an independent US-based Power BI consultancy with no Bullhorn partnership to claim. We build using documented, supported API access under credentials your administrator owns, and everything we produce is published into your own Power BI workspace and belongs to you if we never speak again.
For the agency-wide metric set rather than the source-system plumbing, Power BI dashboards for staffing agencies is the page to read next. Recruiting firms share their underlying economics with other people-based businesses, so professional services dashboards goes deeper on utilization and realization, and service businesses running a PSA instead of an ATS should look at the ConnectWise Power BI dashboard build. Commercial pipeline held in Salesforce or HubSpot can be modelled alongside your placements, the accounting join for burden and AR is covered on our QuickBooks page, and the craft underneath is described in data modeling and DAX and dashboard development. Every task is scoped before work begins from your prepaid block of senior-led hours — no project minimums, no change-order surprises, and hours are deducted only for work you approve.
Book a free demo
Tell us the task. We scope it, you approve, and a senior consultant delivers — typically in 3–5 business days. Prepaid hours that never expire.
The fastest way to reach us is the form — tell us the task and we'll reply within one business day.