Skip to main content
Glama

Analytics Legends — SAP AI & Analytics MCP Server

Public SAP AI & analytics day-rate aggregate

get_day_rate_benchmark
Read-onlyIdempotent

The PUBLIC day-rate aggregate for SAP AI & analytics freelance work: min/max daily rate by country, specialisation and seniority — daily_rate_min / daily_rate_max (plus daily_rate_median, daily_rate_p10, daily_rate_p90 when the source publishes them), every amount in the row's own currency — with source, source date, confidence and the sample the source states. This is the free aggregate published at analyticslegends.ai/api/market-rates.json, and it is SMALL — a few dozen rows at most, every one of them a secondary source (a published market study or a job-board scan), and sample_size is null on most of them. NO COUNT IS WRITTEN HERE ON PURPOSE: _meta.tranche_total_row_count and the rows themselves are the live measure. A frozen pair stood here until 2026-08-27 — '11 rows on 2026-08-09 … sample_size null on 8 of them' — and the second half was WRONG (7 of 11) while the first was still right, which is the whole argument against writing either. NOTHING IS HELD BACK BEHIND IT: there is no paid counterpart to this aggregate. The community-contribution path exists (public.rate_contributions) but publishes nothing yet — v_community_rate_aggregates and v_rate_index are still empty, because a contributed rate only surfaces once a cell holds enough submissions to be reported without identifying anyone. So whatever percentile a source row happens to carry is served here, free, to everyone. The GB row carries a median, p10 and p90, and its own note says its min/max ARE the 25th and 75th percentiles. What is missing from this answer is missing from THIS aggregate; it is not a paid tier. THIS IS NOT THE ONLY RATE THE PLATFORM PUBLISHES, AND ON THE QUESTIONS THIS MARKET ASKS MOST IT IS THE THINNER ONE. The website carries a rate_band per live radar posting for signed-in subscribers — a panel-inferred P25–P75 band per (seniority × product × region) cell, Eursap n=312 plus the Analytics Legends operator panel. This server does not serve it (find_opportunities.rate_band is null on every row, with or without a key). The grid covers EUROPEAN cells only — DACH, FR/Benelux, UK/IE, Southern EU, Eastern EU, Nordics — and there it prices exactly the cells this small aggregate cannot (Senior Datasphere DACH, Senior BDC DACH), where specialisation:"bdc" here returns nothing. When this tool comes back empty for a country × product: in one of those European regions, say the AGGREGATE holds no row and that subscribers read the platform's band on the posting pages; ANYWHERE ELSE (Americas, APAC, Africa, Middle East) say the platform publishes no rate for that cell at any tier — never that subscribers get one. The two are different instruments: this one is a published market study, that one is an editorial benchmark attached to a live posting. Read _meta.available_countries / available_specialisations / available_seniorities — they are computed from the aggregate on every call — before concluding that a rate is unpublished, and quote each row with its own currency, its confidence and its source date.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
countryNoISO-3166-1 alpha-2 country code, e.g. DE, FR, CH.
seniorityNoe.g. senior, principal.
specialisationNoA specialisation code, matched exactly. The live list is `_meta.available_specialisations` on every call — no list is kept here, because a written one went stale. Codes are NOT evenly spread across countries: read the rows a country filter returns. A code this aggregate does not hold (bdc, joule) returns 0 rows and the available codes; it never widens to a neighbouring band. 0 rows here is an absence in THIS aggregate only — see the tool description for what the website's subscriber band covers and where no rate exists at any tier.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / specialisation / description
      Previous value: -"One of the codes the aggregate actually holds — analytics_all, bw, bw4hana, bw4hana_sac, sac, datasphere (2026-08-09; the live list comes back as `_meta.available_specialisations` on every call). They are NOT evenly spread across countries: DE holds analytics_all only, at three seniorities, and datasphere exists for FR alone, as a median with confidence 'low'. There is no bdc row and no joule row in THIS aggregate — but the platform does price BDC: `find_opportunities` carries a panel-inferred band on the live postings, €1,000–€1,400/day P25–P75 for Senior BDC on 52 German postings (2026-08-10). A code this aggregate does not hold returns 0 rows and the available codes; it never widens to a neighbouring band, and 0 here does not mean the platform is silent."New value: +"A specialisation code, matched exactly. The live list is `_meta.available_specialisations` on every call — no list is kept here, because a written one went stale. Codes are NOT evenly spread across countries: read the rows a country filter returns. A code this aggregate does not hold (bdc, joule) returns 0 rows and the available codes; it never widens to a neighbouring band. 0 rows here is an absence in THIS aggregate only — see the tool description for what the website's subscriber band covers and where no rate exists at any tier."
  2. Changed5 schema fields changed
    • addedOutput schema / properties / rows / items / properties / client_market
      Added value: +{
      +  "description": "india_domestic (INR, Indian client) | eu_uk_remote (EUR, India-based contractor billed to an EU/UK client). Set on the India rows only.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_max_inr
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_max + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_median_inr
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_median + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_min_inr
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_min + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • changedOutput schema / properties / rows / items / properties / source_type / description
      Previous value: -"primary_agency_guide | primary_job_ads | aggregator | marketing"New value: +"primary_agency_guide | primary_job_ads | primary_salary_survey | aggregator | marketing"
  3. Changed1 schema field changed
    • changedOutput schema / properties / rows / items / properties / indicative / description
      Previous value: -"true = orientation only: the source is a proxy (non-SAP BI band, generic ERP, agency monthly contract or marketing average). Never quote it as the SAP analytics rate of its country."New value: +"true = orientation only: the source is a proxy (non-SAP BI band, generic ERP, agency monthly contract or marketing average). Never quote it as the SAP AI & analytics rate of its country."
  4. Changed21 schema fields changed
    • addedOutput schema / properties / rows / items / properties / accessed
      Added value: +{
      +  "description": "date the source was read (YYYY-MM-DD)",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / conversion
      Added value: +{
      +  "description": "how an hourly or monthly source figure was turned into a day rate",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_max_aud
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_max + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_max_hkd
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_max + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_max_jpy
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_max + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_max_nzd
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_max + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_max_sgd
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_max + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_median_aud
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_median + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_median_hkd
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_median + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_median_jpy
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_median + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_median_nzd
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_median + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_median_sgd
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_median + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_min_aud
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_min + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_min_hkd
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_min + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_min_jpy
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_min + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_min_nzd
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_min + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_min_sgd
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_min + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / indicative
      Added value: +{
      +  "description": "true = orientation only: the source is a proxy (non-SAP BI band, generic ERP, agency monthly contract or marketing average). Never quote it as the SAP analytics rate of its country.",
      +  "type": [
      +    "boolean",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / sap_scope
      Added value: +{
      +  "description": "what the source actually measures: sap_analytics | sap_general | erp_general | bi_non_sap",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / source_period
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / source_type
      Added value: +{
      +  "description": "primary_agency_guide | primary_job_ads | aggregator | marketing",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
  5. Changed26 schema fields changed
    • addedOutput schema / properties / rows / items / properties / basis
      Added value: +{
      +  "description": "external_published_median | external_published_range | panel — see /api/market-rates.json _meta.basis_values.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / confidence
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / currency / description
      Added value: +"ISO-4217 unit of every daily_rate_* amount on THIS row (EUR, GBP, CHF) — never an envelope constant."
    • addedOutput schema / properties / rows / items / properties / daily_rate_max
      Added value: +{
      +  "description": "Upper bound of the published band, in `currency`.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_max_chf
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_max + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_max_eur
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_max + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_max_gbp
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_max + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_median
      Added value: +{
      +  "description": "Median when the source publishes one, in `currency`.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_median_eur
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_median + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_median_gbp
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_median + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_min
      Added value: +{
      +  "description": "Lower bound of the published band, in `currency`.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_min_chf
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_min + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_min_eur
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_min + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_min_gbp
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_min + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_p10
      Added value: +{
      +  "description": "10th percentile when the source publishes one, in `currency`.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_p10_gbp
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_p10 + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_p90
      Added value: +{
      +  "description": "90th percentile when the source publishes one, in `currency`.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / daily_rate_p90_gbp
      Added value: +{
      +  "description": "DEPRECATED — read daily_rate_p90 + currency.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / note
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / sample_basis
      Added value: +{
      +  "description": "What `sample_size` counts (profiles, vacancies…), when the source says.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / sample_size / description
      Added value: +"The sample the SOURCE states behind this row; null when it states none — never estimated."
    • addedOutput schema / properties / rows / items / properties / source
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / source_date
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / source_url
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / trend
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / yoy_change_pct
      Added value: +{
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
  6. Changed1 schema field changed
    • changedInput schema / properties / specialisation / description
      Previous value: -"One of the codes the aggregate actually holds — analytics_all, bw, bw4hana, bw4hana_sac, sac, datasphere (2026-08-09; the live list comes back as `_meta.available_specialisations` on every call). They are NOT evenly spread across countries: DE holds analytics_all only, at three seniorities, and datasphere exists for FR alone, as a median with confidence 'low'. There is no bdc row and no joule row anywhere in the aggregate — the two products this market asks about most are the two it cannot price. A code the aggregate does not hold returns 0 rows and the available codes; it never widens to a neighbouring band."New value: +"One of the codes the aggregate actually holds — analytics_all, bw, bw4hana, bw4hana_sac, sac, datasphere (2026-08-09; the live list comes back as `_meta.available_specialisations` on every call). They are NOT evenly spread across countries: DE holds analytics_all only, at three seniorities, and datasphere exists for FR alone, as a median with confidence 'low'. There is no bdc row and no joule row in THIS aggregate — but the platform does price BDC: `find_opportunities` carries a panel-inferred band on the live postings, €1,000–€1,400/day P25–P75 for Senior BDC on 52 German postings (2026-08-10). A code this aggregate does not hold returns 0 rows and the available codes; it never widens to a neighbouring band, and 0 here does not mean the platform is silent."
  7. Changed1 schema field changed
    • changedInput schema / properties / specialisation / description
      Previous value: -"e.g. datasphere, bdc, sac, bw4hana, joule."New value: +"One of the codes the aggregate actually holds — analytics_all, bw, bw4hana, bw4hana_sac, sac, datasphere (2026-08-09; the live list comes back as `_meta.available_specialisations` on every call). They are NOT evenly spread across countries: DE holds analytics_all only, at three seniorities, and datasphere exists for FR alone, as a median with confidence 'low'. There is no bdc row and no joule row anywhere in the aggregate — the two products this market asks about most are the two it cannot price. A code the aggregate does not hold returns 0 rows and the available codes; it never widens to a neighbouring band."
  8. Changed6 schema fields changed
    • removedOutput schema / properties / rows / items / properties / citation_note
      Removed value: -{
      -  "type": "string"
      -}
    • changedOutput schema / properties / rows / items / properties / country / type
      Previous value: -"string"New value: +[
      +  "string",
      +  "null"
      +]
    • changedOutput schema / properties / rows / items / properties / currency / type
      Previous value: -"string"New value: +[
      +  "string",
      +  "null"
      +]
    • changedOutput schema / properties / rows / items / properties / sample_size / type
      Previous value: -"integer"New value: +[
      +  "integer",
      +  "null"
      +]
    • changedOutput schema / properties / rows / items / properties / seniority / type
      Previous value: -"string"New value: +[
      +  "string",
      +  "null"
      +]
    • changedOutput schema / properties / rows / items / properties / specialisation / type
      Previous value: -"string"New value: +[
      +  "string",
      +  "null"
      +]
  9. Changed3 schema fields changed
    • addedOutput schema / properties / rows / items / properties / citation_note
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / rows / items / properties / citation_scope
      Added value: +{
      +  "enum": [
      +    "record",
      +    "section_hub"
      +  ],
      +  "type": "string"
      +}
    • addedOutput schema / properties / rows / items / properties / citation_url
      Added value: +{
      +  "type": "string"
      +}
  10. Changed1 schema field changed
    • addedOutput schema / properties / rows / items / properties / currency
      Added value: +{
      +  "type": "string"
      +}
  11. First observed

TDQS

A3.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, openWorld, non-destructive, so the safety profile is covered. The description goes well beyond them: the result set is small (a few dozen rows), every row is a secondary source, sample_size is null on most rows, amounts are in each row's own currency, and bdc/joule return 0 rows rather than widening. This is meaningful disclosure about data shape and limits that the annotations cannot convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in the first clause, but the body is bloated with self-justifying digressions — the 'frozen pair stood here until 2026-08-27 … the second half was WRONG (7 of 11)' anecdote and the argument about why no count is written. These do not help an agent select or invoke the tool and crowd out the genuinely useful routing rules.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-optional-parameter, read-only aggregate with an output schema and full annotation coverage, the description supplies the edge-case semantics an agent most needs: empty-result interpretation differs by region, and _meta.available_* must be read before declaring a rate unpublished. It is arguably over-complete, but nothing an agent needs is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the schema descriptions are themselves rich (the specialisation field already documents exact matching, the live _meta list, and 0-row behaviour). The description largely repeats that advice rather than adding new syntax or format detail, so the baseline 3 applies; the per-row currency guidance is about output, not parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening clause states a specific resource and content: a PUBLIC day-rate aggregate giving min/max daily rate by country, specialisation and seniority. It also explicitly distinguishes itself from the sibling instrument (find_opportunities.rate_band, which it does not serve). The signal is clear but has to be excavated from a great deal of surrounding argument.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives unusually explicit routing rules: when a country × product query returns empty inside a European grid region, say the aggregate holds no row; anywhere else, say no rate is published at any tier. It names the alternative instrument and its European-only coverage. What it lacks is ordinary when-to-call-this guidance relative to siblings like find_opportunities, since the guidance is framed around how to narrate an empty result rather than when to select the tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.