openapi: 3.1.0
info:
  title: harmony Data API
  version: "1.0.0"
  summary: Medical conference and journal data for biopharma teams.
  description: |
    Read-only access to Orbytel Group's curated medical **conference** and **journal** data.

    - **Base URL:** `https://api.orbytelgroup.com/v1`
    - **Authentication:** send your API key as `Authorization: Bearer <key>` (or `X-API-Key: <key>`).
    - **Format:** JSON (UTF-8). Dates are `YYYY-MM-DD`; timestamps are UTC ISO 8601 (`2026-10-20T14:03:11Z`).
      Empty values are `null`.
    - **Keeping a copy in sync:** download the full dataset once from `/exports/{dataset}`, then poll
      `/{dataset}/changes?since=<token>` for additions, updates and removals. See the
      [syncing guide](https://developer.orbytelgroup.com/guides/syncing.html).
    - **Excel and Power BI:** `/exports/{dataset}?format=csv` returns one flat table. See the
      [Excel guide](https://developer.orbytelgroup.com/guides/excel.html).

    Your license decides which datasets your key can read and, for conferences, which therapy areas, countries
    and years. Records outside your license behave as if they don't exist.
  contact:
    name: Orbytel Group
    url: https://orbytelgroup.com/contact-us/
servers:
  - url: https://api.orbytelgroup.com/v1
security:
  - bearerAuth: []
  - apiKeyHeader: []
tags:
  - name: Conferences
    description: Medical meetings, their dates, venues and abstract deadlines.
  - name: Journals
    description: Journal profiles, impact metrics, publishing terms and timelines.
  - name: Sync
    description: Bulk export and the change feed, for keeping your own copy current.
  - name: Reference
    description: Lists of the therapy areas, tags and organizations in your licensed data.
  - name: Service
    description: Health and data freshness.

paths:
  /conferences:
    get:
      tags: [Conferences]
      operationId: listConferences
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl "https://api.orbytelgroup.com/v1/conferences?year=2027&therapy_area=Oncology&limit=100" \
              -H "Authorization: Bearer $ORBYTEL_KEY"
        - lang: Python
          label: Python
          source: |
            import os, requests

            API = "https://api.orbytelgroup.com/v1"
            HEADERS = {"Authorization": "Bearer " + os.environ["ORBYTEL_KEY"]}

            records, cursor = [], None
            while True:
                params = {"year": 2027, "therapy_area": "Oncology", "limit": 100}
                if cursor:
                    params["cursor"] = cursor
                r = requests.get(f"{API}/conferences", params=params, headers=HEADERS, timeout=60)
                r.raise_for_status()
                page = r.json()
                records += page["data"]
                if not page["has_more"]:
                    break
                cursor = page["next_cursor"]

            print(len(records), "records")
        - lang: JavaScript
          label: JavaScript
          source: |
            // Node 18+ (or any browser-style fetch)
            const res = await fetch("https://api.orbytelgroup.com/v1/conferences?" + new URLSearchParams({ year: 2027, therapy_area: "Oncology", limit: 100 }), {
              headers: { Authorization: "Bearer " + process.env.ORBYTEL_KEY },
            });
            if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
            const body = await res.json();
            console.log(body.data.length, "records; next page cursor:", body.next_cursor);
        - lang: PHP
          label: PHP
          source: |
            <?php
            $api = 'https://api.orbytelgroup.com/v1';
            $key = getenv('ORBYTEL_KEY');

            function get_json($url, $key = null)
            {
                $ch = curl_init($url);
                curl_setopt_array($ch, array(
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_HTTPHEADER     => $key ? array('Authorization: Bearer ' . $key) : array(),
                    CURLOPT_ENCODING       => '',      // accept gzip
                    CURLOPT_TIMEOUT        => 60,
                ));
                $body   = curl_exec($ch);
                $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error  = curl_error($ch);
                curl_close($ch);
                if ($body === false) {
                    throw new Exception('Request failed: ' . $error);   // network / TLS problem
                }
                $data = json_decode($body, true);
                if ($status !== 200) {
                    throw new Exception($status . ': ' . (isset($data['detail']) ? $data['detail'] : $body));
                }
                return $data;
            }

            // Every 2027 oncology conference, following next_cursor page by page
            $records = array();
            $params  = array('year' => 2027, 'therapy_area' => 'Oncology', 'limit' => 100);
            do {
                $page    = get_json($api . '/conferences?' . http_build_query($params), $key);
                $records = array_merge($records, $page['data']);
                $params['cursor'] = $page['next_cursor'];
            } while ($page['has_more']);

            echo count($records), " records\n";
      summary: List conferences
      description: Conferences in your license, ordered by `id`, a page at a time. Follow `next_cursor` to get the next page.
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/updated_since'
        - $ref: '#/components/parameters/q'
        - name: year
          in: query
          description: Edition year.
          schema: { type: integer, minimum: 1900, maximum: 2100, example: 2027 }
        - name: start_date_from
          in: query
          description: Meetings starting on or after this date.
          schema: { type: string, format: date, example: '2027-01-01' }
        - name: start_date_to
          in: query
          description: Meetings starting on or before this date.
          schema: { type: string, format: date, example: '2027-06-30' }
        - name: country
          in: query
          description: Host country, exactly as stored (for example `United States`, `Germany`).
          schema: { type: string, example: United States }
        - name: therapy_area
          in: query
          description: One therapy area, for example `Oncology`. See `/reference/therapy-areas`.
          schema: { type: string, example: Oncology }
      responses:
        '200':
          description: A page of conferences.
          headers:
            ETag: { $ref: '#/components/headers/ETag' }
            RateLimit-Limit: { $ref: '#/components/headers/RateLimit-Limit' }
            RateLimit-Remaining: { $ref: '#/components/headers/RateLimit-Remaining' }
            RateLimit-Reset: { $ref: '#/components/headers/RateLimit-Reset' }
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Page'
                  - type: object
                    properties:
                      data: { type: array, items: { $ref: '#/components/schemas/Conference' } }
        '304': { $ref: '#/components/responses/NotModified' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /conferences/{id}:
    get:
      tags: [Conferences]
      operationId: getConference
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl "https://api.orbytelgroup.com/v1/conferences/9995" \
              -H "Authorization: Bearer $ORBYTEL_KEY"
        - lang: Python
          label: Python
          source: |
            import os, requests

            API = "https://api.orbytelgroup.com/v1"
            HEADERS = {"Authorization": "Bearer " + os.environ["ORBYTEL_KEY"]}

            r = requests.get(f"{API}/conferences/9995", headers=HEADERS, timeout=60)
            r.raise_for_status()
            print(r.json()["data"])
        - lang: JavaScript
          label: JavaScript
          source: |
            // Node 18+ (or any browser-style fetch)
            const res = await fetch("https://api.orbytelgroup.com/v1/conferences/9995", {
              headers: { Authorization: "Bearer " + process.env.ORBYTEL_KEY },
            });
            if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
            const body = await res.json();
            console.log(body.data);
        - lang: PHP
          label: PHP
          source: |
            <?php
            $api = 'https://api.orbytelgroup.com/v1';
            $key = getenv('ORBYTEL_KEY');

            function get_json($url, $key = null)
            {
                $ch = curl_init($url);
                curl_setopt_array($ch, array(
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_HTTPHEADER     => $key ? array('Authorization: Bearer ' . $key) : array(),
                    CURLOPT_ENCODING       => '',      // accept gzip
                    CURLOPT_TIMEOUT        => 60,
                ));
                $body   = curl_exec($ch);
                $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error  = curl_error($ch);
                curl_close($ch);
                if ($body === false) {
                    throw new Exception('Request failed: ' . $error);   // network / TLS problem
                }
                $data = json_decode($body, true);
                if ($status !== 200) {
                    throw new Exception($status . ': ' . (isset($data['detail']) ? $data['detail'] : $body));
                }
                return $data;
            }

            $conference = get_json($api . '/conferences/9995', $key)['data'];
            echo $conference['name'], ': ', $conference['start_date'], ' (', $conference['start_date_status'], ")\n";
      summary: Get one conference
      parameters:
        - $ref: '#/components/parameters/id'
      responses:
        '200':
          description: The conference.
          headers:
            ETag: { $ref: '#/components/headers/ETag' }
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data: { $ref: '#/components/schemas/Conference' }
        '304': { $ref: '#/components/responses/NotModified' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /conferences/changes:
    get:
      tags: [Sync, Conferences]
      operationId: conferenceChanges
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            # Use next_sync_token from your last call (or an export's X-Sync-Token)
            curl "https://api.orbytelgroup.com/v1/conferences/changes?since=SAVED_TOKEN" \
              -H "Authorization: Bearer $ORBYTEL_KEY"
        - lang: Python
          label: Python
          source: |
            import os, requests

            API = "https://api.orbytelgroup.com/v1"
            HEADERS = {"Authorization": "Bearer " + os.environ["ORBYTEL_KEY"]}

            token = load_saved_token()   # from your last sync
            while True:
                r = requests.get(f"{API}/conferences/changes", params={"since": token}, headers=HEADERS, timeout=60)
                r.raise_for_status()
                page = r.json()
                for record in page["data"]:
                    if record.get("deleted"):
                        delete_from_my_copy(record["id"])
                    else:
                        upsert_into_my_copy(record)
                token = page["next_sync_token"]
                save_token(token)
                if not page["has_more"]:
                    break
        - lang: JavaScript
          label: JavaScript
          source: |
            // Node 18+ (or any browser-style fetch)
            const res = await fetch("https://api.orbytelgroup.com/v1/conferences/changes?since=" + encodeURIComponent(savedToken), {
              headers: { Authorization: "Bearer " + process.env.ORBYTEL_KEY },
            });
            if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
            const page = await res.json();
            for (const record of page.data) {
              if (record.deleted) deleteFromMyCopy(record.id); else upsertIntoMyCopy(record);
            }
            saveToken(page.next_sync_token);   // call again at once if page.has_more
        - lang: PHP
          label: PHP
          source: |
            <?php
            $api = 'https://api.orbytelgroup.com/v1';
            $key = getenv('ORBYTEL_KEY');

            function get_json($url, $key = null)
            {
                $ch = curl_init($url);
                curl_setopt_array($ch, array(
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_HTTPHEADER     => $key ? array('Authorization: Bearer ' . $key) : array(),
                    CURLOPT_ENCODING       => '',      // accept gzip
                    CURLOPT_TIMEOUT        => 60,
                ));
                $body   = curl_exec($ch);
                $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error  = curl_error($ch);
                curl_close($ch);
                if ($body === false) {
                    throw new Exception('Request failed: ' . $error);   // network / TLS problem
                }
                $data = json_decode($body, true);
                if ($status !== 200) {
                    throw new Exception($status . ': ' . (isset($data['detail']) ? $data['detail'] : $body));
                }
                return $data;
            }

            // Apply everything that changed since your last sync, then save the new token
            $token = load_saved_token();   // next_sync_token from last time, or an export's X-Sync-Token
            do {
                $page = get_json($api . '/conferences/changes?' . http_build_query(array('since' => $token)), $key);
                foreach ($page['data'] as $record) {
                    if (!empty($record['deleted'])) {
                        delete_from_my_copy($record['id']);   // fine if you never had it
                    } else {
                        upsert_into_my_copy($record);
                    }
                }
                $token = $page['next_sync_token'];
                save_token($token);
            } while ($page['has_more']);
      summary: Conference changes since a sync token
      description: |
        Every conference that was added, changed or removed after your token, oldest change first. Each
        record appears once, in its current state; a removed record (or one that left your license) appears as
        `{ "id": …, "deleted": true, "changed_at": … }`.

        Without `since` or `updated_since`, the response is empty and `next_sync_token` marks "now".
        Keep calling with `next_sync_token` while `has_more` is true. Changes appear about a minute after they're
        saved, and a record can occasionally be delivered twice; apply every entry as "replace my copy with this".
      parameters:
        - $ref: '#/components/parameters/since'
        - $ref: '#/components/parameters/changes_updated_since'
        - $ref: '#/components/parameters/changes_limit'
      responses:
        '200':
          description: Changes since the token.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ChangePage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          oneOf:
                            - $ref: '#/components/schemas/Conference'
                            - $ref: '#/components/schemas/Tombstone'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '410': { $ref: '#/components/responses/ResyncRequired' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /journals:
    get:
      tags: [Journals]
      operationId: listJournals
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl "https://api.orbytelgroup.com/v1/journals?q=oncology&limit=100" \
              -H "Authorization: Bearer $ORBYTEL_KEY"
        - lang: Python
          label: Python
          source: |
            import os, requests

            API = "https://api.orbytelgroup.com/v1"
            HEADERS = {"Authorization": "Bearer " + os.environ["ORBYTEL_KEY"]}

            records, cursor = [], None
            while True:
                params = {"q": "oncology", "limit": 100}
                if cursor:
                    params["cursor"] = cursor
                r = requests.get(f"{API}/journals", params=params, headers=HEADERS, timeout=60)
                r.raise_for_status()
                page = r.json()
                records += page["data"]
                if not page["has_more"]:
                    break
                cursor = page["next_cursor"]

            print(len(records), "records")
        - lang: JavaScript
          label: JavaScript
          source: |
            // Node 18+ (or any browser-style fetch)
            const res = await fetch("https://api.orbytelgroup.com/v1/journals?" + new URLSearchParams({ q: "oncology", limit: 100 }), {
              headers: { Authorization: "Bearer " + process.env.ORBYTEL_KEY },
            });
            if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
            const body = await res.json();
            console.log(body.data.length, "records; next page cursor:", body.next_cursor);
        - lang: PHP
          label: PHP
          source: |
            <?php
            $api = 'https://api.orbytelgroup.com/v1';
            $key = getenv('ORBYTEL_KEY');

            function get_json($url, $key = null)
            {
                $ch = curl_init($url);
                curl_setopt_array($ch, array(
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_HTTPHEADER     => $key ? array('Authorization: Bearer ' . $key) : array(),
                    CURLOPT_ENCODING       => '',      // accept gzip
                    CURLOPT_TIMEOUT        => 60,
                ));
                $body   = curl_exec($ch);
                $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error  = curl_error($ch);
                curl_close($ch);
                if ($body === false) {
                    throw new Exception('Request failed: ' . $error);   // network / TLS problem
                }
                $data = json_decode($body, true);
                if ($status !== 200) {
                    throw new Exception($status . ': ' . (isset($data['detail']) ? $data['detail'] : $body));
                }
                return $data;
            }

            $records = array();
            $params  = array('q' => 'oncology', 'limit' => 100);
            do {
                $page    = get_json($api . '/journals?' . http_build_query($params), $key);
                $records = array_merge($records, $page['data']);
                $params['cursor'] = $page['next_cursor'];
            } while ($page['has_more']);

            echo count($records), " journals\n";
      summary: List journals
      description: Journals in your license, ordered by `id`, a page at a time.
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/updated_since'
        - $ref: '#/components/parameters/q'
      responses:
        '200':
          description: A page of journals.
          headers:
            ETag: { $ref: '#/components/headers/ETag' }
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Page'
                  - type: object
                    properties:
                      data: { type: array, items: { $ref: '#/components/schemas/Journal' } }
        '304': { $ref: '#/components/responses/NotModified' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /journals/{id}:
    get:
      tags: [Journals]
      operationId: getJournal
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl "https://api.orbytelgroup.com/v1/journals/489" \
              -H "Authorization: Bearer $ORBYTEL_KEY"
        - lang: Python
          label: Python
          source: |
            import os, requests

            API = "https://api.orbytelgroup.com/v1"
            HEADERS = {"Authorization": "Bearer " + os.environ["ORBYTEL_KEY"]}

            r = requests.get(f"{API}/journals/489", headers=HEADERS, timeout=60)
            r.raise_for_status()
            print(r.json()["data"])
        - lang: JavaScript
          label: JavaScript
          source: |
            // Node 18+ (or any browser-style fetch)
            const res = await fetch("https://api.orbytelgroup.com/v1/journals/489", {
              headers: { Authorization: "Bearer " + process.env.ORBYTEL_KEY },
            });
            if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
            const body = await res.json();
            console.log(body.data);
        - lang: PHP
          label: PHP
          source: |
            <?php
            $api = 'https://api.orbytelgroup.com/v1';
            $key = getenv('ORBYTEL_KEY');

            function get_json($url, $key = null)
            {
                $ch = curl_init($url);
                curl_setopt_array($ch, array(
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_HTTPHEADER     => $key ? array('Authorization: Bearer ' . $key) : array(),
                    CURLOPT_ENCODING       => '',      // accept gzip
                    CURLOPT_TIMEOUT        => 60,
                ));
                $body   = curl_exec($ch);
                $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error  = curl_error($ch);
                curl_close($ch);
                if ($body === false) {
                    throw new Exception('Request failed: ' . $error);   // network / TLS problem
                }
                $data = json_decode($body, true);
                if ($status !== 200) {
                    throw new Exception($status . ': ' . (isset($data['detail']) ? $data['detail'] : $body));
                }
                return $data;
            }

            $journal = get_json($api . '/journals/489', $key)['data'];
            echo $journal['name'], ': impact factor ', $journal['impact_factor'], "\n";
      summary: Get one journal
      parameters:
        - $ref: '#/components/parameters/id'
      responses:
        '200':
          description: The journal.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data: { $ref: '#/components/schemas/Journal' }
        '304': { $ref: '#/components/responses/NotModified' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /journals/changes:
    get:
      tags: [Sync, Journals]
      operationId: journalChanges
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            # Use next_sync_token from your last call (or an export's X-Sync-Token)
            curl "https://api.orbytelgroup.com/v1/journals/changes?since=SAVED_TOKEN" \
              -H "Authorization: Bearer $ORBYTEL_KEY"
        - lang: Python
          label: Python
          source: |
            import os, requests

            API = "https://api.orbytelgroup.com/v1"
            HEADERS = {"Authorization": "Bearer " + os.environ["ORBYTEL_KEY"]}

            token = load_saved_token()   # from your last sync
            while True:
                r = requests.get(f"{API}/journals/changes", params={"since": token}, headers=HEADERS, timeout=60)
                r.raise_for_status()
                page = r.json()
                for record in page["data"]:
                    if record.get("deleted"):
                        delete_from_my_copy(record["id"])
                    else:
                        upsert_into_my_copy(record)
                token = page["next_sync_token"]
                save_token(token)
                if not page["has_more"]:
                    break
        - lang: JavaScript
          label: JavaScript
          source: |
            // Node 18+ (or any browser-style fetch)
            const res = await fetch("https://api.orbytelgroup.com/v1/journals/changes?since=" + encodeURIComponent(savedToken), {
              headers: { Authorization: "Bearer " + process.env.ORBYTEL_KEY },
            });
            if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
            const page = await res.json();
            for (const record of page.data) {
              if (record.deleted) deleteFromMyCopy(record.id); else upsertIntoMyCopy(record);
            }
            saveToken(page.next_sync_token);   // call again at once if page.has_more
        - lang: PHP
          label: PHP
          source: |
            <?php
            $api = 'https://api.orbytelgroup.com/v1';
            $key = getenv('ORBYTEL_KEY');

            function get_json($url, $key = null)
            {
                $ch = curl_init($url);
                curl_setopt_array($ch, array(
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_HTTPHEADER     => $key ? array('Authorization: Bearer ' . $key) : array(),
                    CURLOPT_ENCODING       => '',      // accept gzip
                    CURLOPT_TIMEOUT        => 60,
                ));
                $body   = curl_exec($ch);
                $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error  = curl_error($ch);
                curl_close($ch);
                if ($body === false) {
                    throw new Exception('Request failed: ' . $error);   // network / TLS problem
                }
                $data = json_decode($body, true);
                if ($status !== 200) {
                    throw new Exception($status . ': ' . (isset($data['detail']) ? $data['detail'] : $body));
                }
                return $data;
            }

            // Apply everything that changed since your last sync, then save the new token
            $token = load_saved_token();   // next_sync_token from last time, or an export's X-Sync-Token
            do {
                $page = get_json($api . '/journals/changes?' . http_build_query(array('since' => $token)), $key);
                foreach ($page['data'] as $record) {
                    if (!empty($record['deleted'])) {
                        delete_from_my_copy($record['id']);   // fine if you never had it
                    } else {
                        upsert_into_my_copy($record);
                    }
                }
                $token = $page['next_sync_token'];
                save_token($token);
            } while ($page['has_more']);
      summary: Journal changes since a sync token
      description: Works exactly like `/conferences/changes`.
      parameters:
        - $ref: '#/components/parameters/since'
        - $ref: '#/components/parameters/changes_updated_since'
        - $ref: '#/components/parameters/changes_limit'
      responses:
        '200':
          description: Changes since the token.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ChangePage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          oneOf:
                            - $ref: '#/components/schemas/Journal'
                            - $ref: '#/components/schemas/Tombstone'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '410': { $ref: '#/components/responses/ResyncRequired' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /exports/{dataset}:
    get:
      tags: [Sync]
      operationId: exportDataset
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl --compressed -o conferences.csv "https://api.orbytelgroup.com/v1/exports/conferences?format=csv" \
              -H "Authorization: Bearer $ORBYTEL_KEY"
        - lang: Python
          label: Python
          source: |
            import os, requests

            API = "https://api.orbytelgroup.com/v1"
            HEADERS = {"Authorization": "Bearer " + os.environ["ORBYTEL_KEY"]}

            r = requests.get(f"{API}/exports/conferences", params={"format": "csv"}, headers=HEADERS, timeout=300)
            r.raise_for_status()
            open("conferences.csv", "wb").write(r.content)
            sync_token = r.headers["X-Sync-Token"]   # continue from here with /conferences/changes

            # Or the default JSON Lines file (gzip): omit format=csv, then
            # for line in gzip.decompress(r.content).splitlines(): record = json.loads(line)
        - lang: JavaScript
          label: JavaScript
          source: |
            // Node 18+
            import { writeFile } from "node:fs/promises";

            const res = await fetch("https://api.orbytelgroup.com/v1/exports/conferences?" + new URLSearchParams({ format: "csv" }), {
              headers: { Authorization: "Bearer " + process.env.ORBYTEL_KEY },
            });
            if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
            await writeFile("conferences.csv", Buffer.from(await res.arrayBuffer()));
            const syncToken = res.headers.get("X-Sync-Token");
        - lang: PHP
          label: PHP
          source: |
            <?php
            $api = 'https://api.orbytelgroup.com/v1';
            $key = getenv('ORBYTEL_KEY');

            // Download the whole licensed dataset as one CSV and keep the sync token it was built at
            $out = fopen('conferences.csv', 'w');
            $syncToken = null;
            $ch = curl_init($api . '/exports/conferences?format=csv');
            curl_setopt_array($ch, array(
                CURLOPT_HTTPHEADER     => array('Authorization: Bearer ' . $key),
                CURLOPT_ENCODING       => '',        // the CSV is sent gzip-compressed
                CURLOPT_FILE           => $out,
                CURLOPT_TIMEOUT        => 300,
                CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$syncToken) {
                    if (stripos($line, 'X-Sync-Token:') === 0) { $syncToken = trim(substr($line, 13)); }
                    return strlen($line);
                },
            ));
            curl_exec($ch);
            $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            fclose($out);
            if ($status !== 200) { throw new Exception("Export failed with HTTP $status"); }

            // Continue from here with /conferences/changes?since=$syncToken
            echo "Saved conferences.csv; sync token $syncToken\n";
        - lang: PowerQuery
          label: Excel (Power Query)
          source: |
            // Excel / Power BI: Data > Get Data > Blank Query > Advanced Editor
            let
                ApiKey = "PASTE-YOUR-KEY-HERE",
                Source = Web.Contents("https://api.orbytelgroup.com/v1/exports/conferences",
                    [ Query = [ format = "csv" ], Headers = [ Authorization = "Bearer " & ApiKey ] ]),
                Csv    = Csv.Document(Source, [ Delimiter = ",", Encoding = 65001, QuoteStyle = QuoteStyle.Csv ]),
                Table  = Table.PromoteHeaders(Csv, [ PromoteAllScalars = true ])
            in
                Table
      summary: Download the full dataset
      description: |
        Every record in your license in one file.

        - `format=jsonl` (default): gzip-compressed JSON Lines, one record per line, the same shape as the list
          endpoints.
        - `format=csv`: one flat table for Excel, Power BI and databases. Nested fields become columns
          (`formats.oral` → `formats_oral`, `timelines.online_submit_to_accept.min_weeks` →
          `timelines_online_submit_to_accept_min_weeks`). Lists become text separated by `; `: `therapy_areas`,
          `organizations` (names) and `tags` (`Type: Name`). UTF-8 with a byte-order mark; `TRUE`/`FALSE` for
          booleans.

        The `X-Sync-Token` response header is the change-feed position the file was built at: load the file,
        then call `/{dataset}/changes?since=<X-Sync-Token>`.
      parameters:
        - name: dataset
          in: path
          required: true
          schema: { type: string, enum: [conferences, journals] }
        - name: format
          in: query
          schema: { type: string, enum: [jsonl, csv], default: jsonl }
      responses:
        '200':
          description: The export file.
          headers:
            X-Sync-Token:
              description: Change-feed position this file reflects.
              schema: { type: string }
            X-Record-Count:
              description: Number of records in the file.
              schema: { type: integer }
          content:
            application/gzip:
              schema: { type: string, format: binary }
            text/csv:
              schema: { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /reference/therapy-areas:
    get:
      tags: [Reference]
      operationId: therapyAreas
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl "https://api.orbytelgroup.com/v1/reference/therapy-areas" \
              -H "Authorization: Bearer $ORBYTEL_KEY"
        - lang: Python
          label: Python
          source: |
            import os, requests

            API = "https://api.orbytelgroup.com/v1"
            HEADERS = {"Authorization": "Bearer " + os.environ["ORBYTEL_KEY"]}

            r = requests.get(f"{API}/reference/therapy-areas", headers=HEADERS, timeout=60)
            r.raise_for_status()
            print(r.json()["data"])
        - lang: JavaScript
          label: JavaScript
          source: |
            // Node 18+ (or any browser-style fetch)
            const res = await fetch("https://api.orbytelgroup.com/v1/reference/therapy-areas", {
              headers: { Authorization: "Bearer " + process.env.ORBYTEL_KEY },
            });
            if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
            const body = await res.json();
            console.log(body.data);
        - lang: PHP
          label: PHP
          source: |
            <?php
            $api = 'https://api.orbytelgroup.com/v1';
            $key = getenv('ORBYTEL_KEY');

            function get_json($url, $key = null)
            {
                $ch = curl_init($url);
                curl_setopt_array($ch, array(
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_HTTPHEADER     => $key ? array('Authorization: Bearer ' . $key) : array(),
                    CURLOPT_ENCODING       => '',      // accept gzip
                    CURLOPT_TIMEOUT        => 60,
                ));
                $body   = curl_exec($ch);
                $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error  = curl_error($ch);
                curl_close($ch);
                if ($body === false) {
                    throw new Exception('Request failed: ' . $error);   // network / TLS problem
                }
                $data = json_decode($body, true);
                if ($status !== 200) {
                    throw new Exception($status . ': ' . (isset($data['detail']) ? $data['detail'] : $body));
                }
                return $data;
            }

            foreach (get_json($api . '/reference/therapy-areas', $key)['data'] as $area) {
                echo $area['name'], ': ', $area['conferences'], " conferences\n";
            }
      summary: Therapy areas
      description: The therapy areas on conferences in your license, with how many conferences have each.
      responses:
        '200':
          description: Therapy areas, alphabetical.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        name: { type: string, example: Oncology }
                        conferences: { type: integer, example: 490 }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }

  /reference/tags:
    get:
      tags: [Reference]
      operationId: tags
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl "https://api.orbytelgroup.com/v1/reference/tags?dataset=conferences&type=Topic%20Area" \
              -H "Authorization: Bearer $ORBYTEL_KEY"
        - lang: Python
          label: Python
          source: |
            import os, requests

            API = "https://api.orbytelgroup.com/v1"
            HEADERS = {"Authorization": "Bearer " + os.environ["ORBYTEL_KEY"]}

            r = requests.get(f"{API}/reference/tags", params={"dataset": "conferences", "type": "Topic Area"}, headers=HEADERS, timeout=60)
            r.raise_for_status()
            print(r.json()["data"])
        - lang: JavaScript
          label: JavaScript
          source: |
            // Node 18+ (or any browser-style fetch)
            const res = await fetch("https://api.orbytelgroup.com/v1/reference/tags?" + new URLSearchParams({ dataset: "conferences", type: "Topic Area" }), {
              headers: { Authorization: "Bearer " + process.env.ORBYTEL_KEY },
            });
            if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
            const body = await res.json();
            console.log(body.data);
        - lang: PHP
          label: PHP
          source: |
            <?php
            $api = 'https://api.orbytelgroup.com/v1';
            $key = getenv('ORBYTEL_KEY');

            function get_json($url, $key = null)
            {
                $ch = curl_init($url);
                curl_setopt_array($ch, array(
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_HTTPHEADER     => $key ? array('Authorization: Bearer ' . $key) : array(),
                    CURLOPT_ENCODING       => '',      // accept gzip
                    CURLOPT_TIMEOUT        => 60,
                ));
                $body   = curl_exec($ch);
                $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error  = curl_error($ch);
                curl_close($ch);
                if ($body === false) {
                    throw new Exception('Request failed: ' . $error);   // network / TLS problem
                }
                $data = json_decode($body, true);
                if ($status !== 200) {
                    throw new Exception($status . ': ' . (isset($data['detail']) ? $data['detail'] : $body));
                }
                return $data;
            }

            $url = $api . '/reference/tags?' . http_build_query(array('dataset' => 'conferences', 'type' => 'Topic Area'));
            foreach (get_json($url, $key)['data'] as $tag) {
                echo $tag['name'], ' (', $tag['records'], ")\n";
            }
      summary: Tags
      description: Tags on records in your license, with how many records carry each.
      parameters:
        - name: dataset
          in: query
          required: true
          schema: { type: string, enum: [conferences, journals] }
        - name: type
          in: query
          description: Only this tag type, for example `Topic Area`, `Audience` or `Attendee Geography`.
          schema: { type: string }
      responses:
        '200':
          description: Tags, by type then name.
          content:
            application/json:
              example: {"data":[{"id":6301,"type":"Topic Area","name":"Acquired Immunodeficiency Syndrome","category":null,"records":139},{"id":6302,"type":"Topic Area","name":"Allergy & Immunology","category":null,"records":238},{"id":6303,"type":"Topic Area","name":"Anesthesiology & Pain Management","category":null,"records":186}]}
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      allOf:
                        - $ref: '#/components/schemas/Tag'
                        - type: object
                          properties:
                            records: { type: integer }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }

  /reference/organizations:
    get:
      tags: [Reference]
      operationId: organizations
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl "https://api.orbytelgroup.com/v1/reference/organizations?dataset=journals" \
              -H "Authorization: Bearer $ORBYTEL_KEY"
        - lang: Python
          label: Python
          source: |
            import os, requests

            API = "https://api.orbytelgroup.com/v1"
            HEADERS = {"Authorization": "Bearer " + os.environ["ORBYTEL_KEY"]}

            r = requests.get(f"{API}/reference/organizations", params={"dataset": "journals"}, headers=HEADERS, timeout=60)
            r.raise_for_status()
            print(r.json()["data"])
        - lang: JavaScript
          label: JavaScript
          source: |
            // Node 18+ (or any browser-style fetch)
            const res = await fetch("https://api.orbytelgroup.com/v1/reference/organizations?" + new URLSearchParams({ dataset: "journals" }), {
              headers: { Authorization: "Bearer " + process.env.ORBYTEL_KEY },
            });
            if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
            const body = await res.json();
            console.log(body.data);
        - lang: PHP
          label: PHP
          source: |
            <?php
            $api = 'https://api.orbytelgroup.com/v1';
            $key = getenv('ORBYTEL_KEY');

            function get_json($url, $key = null)
            {
                $ch = curl_init($url);
                curl_setopt_array($ch, array(
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_HTTPHEADER     => $key ? array('Authorization: Bearer ' . $key) : array(),
                    CURLOPT_ENCODING       => '',      // accept gzip
                    CURLOPT_TIMEOUT        => 60,
                ));
                $body   = curl_exec($ch);
                $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error  = curl_error($ch);
                curl_close($ch);
                if ($body === false) {
                    throw new Exception('Request failed: ' . $error);   // network / TLS problem
                }
                $data = json_decode($body, true);
                if ($status !== 200) {
                    throw new Exception($status . ': ' . (isset($data['detail']) ? $data['detail'] : $body));
                }
                return $data;
            }

            $url = $api . '/reference/organizations?' . http_build_query(array('dataset' => 'journals'));
            foreach (get_json($url, $key)['data'] as $org) {
                echo $org['name'], ' (', $org['records'], ")\n";
            }
      summary: Organizations
      description: Organizations linked to records in your license, with how many records each is linked to.
      parameters:
        - name: dataset
          in: query
          required: true
          schema: { type: string, enum: [conferences, journals] }
      responses:
        '200':
          description: Organizations, by name.
          content:
            application/json:
              example: {"data":[{"id":39971,"name":"American Chemical Society","abbv":"ACS","type":null,"url":"https://www.acs.org","records":20},{"id":40066,"name":"American Physiological Society","abbv":"APS","type":null,"url":"https://www.physiology.org/?SSO=Y","records":13},{"id":41972,"name":"American Medical Association","abbv":"AMA","type":null,"url":"https://www.ama-assn.org/","records":13}]}
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      allOf:
                        - $ref: '#/components/schemas/Organization'
                        - type: object
                          properties:
                            records: { type: integer }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }

  /status:
    get:
      tags: [Service]
      operationId: status
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl "https://api.orbytelgroup.com/v1/status"
        - lang: Python
          label: Python
          source: |
            import requests

            API = "https://api.orbytelgroup.com/v1"

            r = requests.get(f"{API}/status", timeout=60)
            r.raise_for_status()
            print(r.json())
        - lang: JavaScript
          label: JavaScript
          source: |
            // Node 18+ (or any browser-style fetch)
            const res = await fetch("https://api.orbytelgroup.com/v1/status");
            const body = await res.json();
            console.log(body);
        - lang: PHP
          label: PHP
          source: |
            <?php
            $api = 'https://api.orbytelgroup.com/v1';

            function get_json($url, $key = null)
            {
                $ch = curl_init($url);
                curl_setopt_array($ch, array(
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_HTTPHEADER     => $key ? array('Authorization: Bearer ' . $key) : array(),
                    CURLOPT_ENCODING       => '',      // accept gzip
                    CURLOPT_TIMEOUT        => 60,
                ));
                $body   = curl_exec($ch);
                $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error  = curl_error($ch);
                curl_close($ch);
                if ($body === false) {
                    throw new Exception('Request failed: ' . $error);   // network / TLS problem
                }
                $data = json_decode($body, true);
                if ($status !== 200) {
                    throw new Exception($status . ': ' . (isset($data['detail']) ? $data['detail'] : $body));
                }
                return $data;
            }

            $status = get_json($api . '/status');   // no key needed
            echo $status['status'], ': ', $status['datasets']['conferences']['records'], " conferences\n";
      summary: Service status
      description: Whether the API is up, how many records each dataset holds and when data last changed. No key needed.
      security: []
      responses:
        '200':
          description: The API is up.
          content:
            application/json:
              example: {"status":"ok","api_version":"v1","time":"2026-10-20T22:16:35Z","datasets":{"conferences":{"records":9029,"last_change_at":"2026-10-20T19:56:48Z"},"journals":{"records":2954,"last_change_at":"2026-10-20T19:56:48Z"}}}
              schema:
                type: object
                properties:
                  status: { type: string, example: ok }
                  api_version: { type: string, example: v1 }
                  time: { type: string, format: date-time }
                  datasets:
                    type: object
                    additionalProperties:
                      type: object
                      properties:
                        records: { type: integer }
                        last_change_at: { type: [string, 'null'], format: date-time }
        '503': { $ref: '#/components/responses/Unavailable' }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: '`Authorization: Bearer <your key>`. Keys start with `odk_live_`.'
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: The same key, for tools that can't set an Authorization header.

  parameters:
    id:
      name: id
      in: path
      required: true
      schema: { type: integer, example: 9995 }
    limit:
      name: limit
      in: query
      description: Records per page.
      schema: { type: integer, minimum: 1, maximum: 500, default: 100 }
    cursor:
      name: cursor
      in: query
      description: '`next_cursor` from the previous page.'
      schema: { type: string }
    updated_since:
      name: updated_since
      in: query
      description: Only records added or changed since this date or time (ISO 8601; UTC when no offset is given).
      schema: { type: string, example: '2026-10-01' }
    q:
      name: q
      in: query
      description: Text to find in the name or abbreviation (2–100 characters).
      schema: { type: string, example: ASCO }
    since:
      name: since
      in: query
      description: A sync token from `next_sync_token`, or the `X-Sync-Token` header of an export.
      schema: { type: string }
    changes_updated_since:
      name: updated_since
      in: query
      description: Instead of a token, start from this date or time. Removals since then are included.
      schema: { type: string, example: '2026-10-19T00:00:00Z' }
    changes_limit:
      name: limit
      in: query
      description: Records per page.
      schema: { type: integer, minimum: 1, maximum: 1000, default: 500 }

  headers:
    ETag:
      description: Send it back as `If-None-Match` to get `304 Not Modified` when nothing changed.
      schema: { type: string }
    RateLimit-Limit:
      description: Requests allowed per minute for your key.
      schema: { type: integer }
    RateLimit-Remaining:
      description: Requests left in the current minute.
      schema: { type: integer }
    RateLimit-Reset:
      description: Seconds until the minute resets.
      schema: { type: integer }

  responses:
    NotModified:
      description: Nothing changed since the ETag you sent.
    # Each error response has examples with the API's real messages (core/library/data_api/*,
    # api_v1.php), so every status tab shows its own sample. Keep them in step with the code.
    BadRequest:
      description: A parameter is missing, unknown or invalid. `detail` says which and what to send instead.
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
          examples:
            invalid_parameter:
              summary: A parameter has the wrong form
              value:
                type: https://developer.orbytelgroup.com/errors/invalid_parameter
                title: Bad Request
                status: 400
                detail: '"year" must be a whole number from 1900 to 2100.'
                request_id: 3db3a78eb7aedbbe37742929e9daf451
            unknown_parameter:
              summary: A parameter this endpoint doesn't take
              value:
                type: https://developer.orbytelgroup.com/errors/unknown_parameter
                title: Bad Request
                status: 400
                detail: 'Unknown parameter "page". Allowed here: limit, cursor, updated_since, q, year, start_date_from, start_date_to, country, therapy_area.'
                request_id: 5f0c1e9d2a7b4c3e8d6f1a2b3c4d5e6f
            invalid_cursor:
              summary: A cursor this API didn't give out
              value:
                type: https://developer.orbytelgroup.com/errors/invalid_cursor
                title: Bad Request
                status: 400
                detail: The "cursor" value is not valid. Use next_cursor from the previous page.
                request_id: 8a1b2c3d4e5f60718293a4b5c6d7e8f9
            invalid_sync_token:
              summary: A "since" value that isn't a sync token
              value:
                type: https://developer.orbytelgroup.com/errors/invalid_sync_token
                title: Bad Request
                status: 400
                detail: The "since" value is not a sync token from this API. Use next_sync_token from a previous response, or omit "since" to get the current token.
                request_id: 2e4f6a8c0b1d3f5a7c9e1b3d5f7a9c0e
    Unauthorized:
      description: The key is missing, wrong, revoked or expired.
      headers:
        WWW-Authenticate:
          description: '`Bearer realm="harmony Data API"` (with `error="invalid_token"` when a key was sent but is not usable).'
          schema: { type: string }
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
          examples:
            missing_api_key:
              summary: No key sent
              value:
                type: https://developer.orbytelgroup.com/errors/missing_api_key
                title: Unauthorized
                status: 401
                detail: 'Send your API key in the Authorization header: "Authorization: Bearer <key>".'
                request_id: 3db3a78eb7aedbbe37742929e9daf451
            invalid_api_key:
              summary: Key not recognised
              value:
                type: https://developer.orbytelgroup.com/errors/invalid_api_key
                title: Unauthorized
                status: 401
                detail: This API key is not recognised. Check that it was copied in full.
                request_id: ac5f1e0b9d8c7b6a5f4e3d2c1b0a9f8e
            revoked_api_key:
              summary: Key revoked or replaced
              value:
                type: https://developer.orbytelgroup.com/errors/revoked_api_key
                title: Unauthorized
                status: 401
                detail: This API key has been revoked. Contact Orbytel Group for a new key.
                request_id: 7d6c5b4a39281706f5e4d3c2b1a09f8e
            expired_api_key:
              summary: Trial or time-limited key expired
              value:
                type: https://developer.orbytelgroup.com/errors/expired_api_key
                title: Unauthorized
                status: 401
                detail: This API key expired on 2026-12-31. Contact Orbytel Group to extend it.
                request_id: 1a2b3c4d5e6f708192a3b4c5d6e7f809
    Forbidden:
      description: Your license doesn't include this dataset, or the key can't be used from your IP address.
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
          examples:
            dataset_not_licensed:
              summary: Dataset not in your license
              value:
                type: https://developer.orbytelgroup.com/errors/dataset_not_licensed
                title: Forbidden
                status: 403
                detail: This API key doesn't include the Journals dataset. Contact Orbytel Group to add it to your license.
                request_id: 9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b
            ip_not_allowed:
              summary: Request from an address the key is not allowed to use
              value:
                type: https://developer.orbytelgroup.com/errors/ip_not_allowed
                title: Forbidden
                status: 403
                detail: This API key cannot be used from 203.0.113.7. Ask Orbytel Group to add the address to the key's allowed list.
                request_id: 4c3b2a19f8e7d6c5b4a3928170f6e5d4
    NotFound:
      description: No such record in your license (it may have been removed; check the change feed), or no such endpoint.
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
          examples:
            record_not_found:
              summary: No record with this id in your license
              value:
                type: https://developer.orbytelgroup.com/errors/record_not_found
                title: Not Found
                status: 404
                detail: No conference with id 1 is available to this key. It may have been removed; check the change feed.
                request_id: 6f5e4d3c2b1a09f8e7d6c5b4a3928170
            not_found:
              summary: No such endpoint
              value:
                type: https://developer.orbytelgroup.com/errors/not_found
                title: Not Found
                status: 404
                detail: 'No endpoint at /v1/conference. Available: /v1/conferences, /v1/journals, /v1/{dataset}/{id}, /v1/{dataset}/changes, /v1/exports/{dataset}, /v1/reference/..., /v1/status.'
                request_id: b0a9f8e7d6c5b4a3928170f6e5d4c3b2
    ResyncRequired:
      description: Your license changed after this token was issued. Reload `/exports/{dataset}` and continue from its `X-Sync-Token`.
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
          examples:
            resync_required:
              summary: License changed since the token was issued
              value:
                type: https://developer.orbytelgroup.com/errors/resync_required
                title: Gone
                status: 410
                detail: Your license changed after this sync token was issued. Reload the full dataset from /v1/exports/conferences and continue from its X-Sync-Token.
                request_id: d4c3b2a19f8e7d6c5b4a3928170f6e5d
    TooManyRequests:
      description: Over your per-minute limit. Wait `Retry-After` seconds, then carry on.
      headers:
        Retry-After:
          description: Seconds until you can send requests again.
          schema: { type: integer, example: 27 }
        RateLimit-Limit: { $ref: '#/components/headers/RateLimit-Limit' }
        RateLimit-Remaining: { $ref: '#/components/headers/RateLimit-Remaining' }
        RateLimit-Reset: { $ref: '#/components/headers/RateLimit-Reset' }
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
          examples:
            rate_limited:
              summary: Over the per-minute limit
              value:
                type: https://developer.orbytelgroup.com/errors/rate_limited
                title: Too Many Requests
                status: 429
                detail: This key allows 60 requests per minute. Wait 27 seconds and try again.
                request_id: e5d4c3b2a19f8e7d6c5b4a3928170f6e
    Unavailable:
      description: Temporarily unavailable. Retry after `Retry-After` seconds.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema: { type: integer, example: 60 }
      content:
        application/problem+json:
          schema: { $ref: '#/components/schemas/Problem' }
          examples:
            service_unavailable:
              summary: Temporarily unavailable
              value:
                type: https://developer.orbytelgroup.com/errors/service_unavailable
                title: Service Unavailable
                status: 503
                detail: The Data API is temporarily unavailable. Try again in a minute.
                request_id: f6e5d4c3b2a19f8e7d6c5b4a39281706

  schemas:
    Problem:
      type: object
      description: An error (RFC 9457).
      required: [type, title, status, detail, request_id]
      properties:
        type: { type: string, format: uri, example: 'https://developer.orbytelgroup.com/errors/invalid_parameter' }
        title: { type: string, example: Bad Request }
        status: { type: integer, example: 400 }
        detail: { type: string, example: '"year" must be a whole number from 1900 to 2100.' }
        request_id: { type: string, description: Quote this if you contact support., example: 3db3a78eb7aedbbe37742929e9daf451 }

    Page:
      type: object
      required: [data, has_more, next_cursor]
      properties:
        has_more: { type: boolean }
        next_cursor: { type: [string, 'null'], description: Pass as `cursor` for the next page; null on the last page., example: YzE6MTAwMDQ }

    ChangePage:
      type: object
      required: [data, has_more, next_sync_token]
      properties:
        has_more: { type: boolean, description: Call again with `next_sync_token` straight away if true. }
        next_sync_token: { type: string, description: Save it and pass it as `since` next time., example: MToyMDQ4NToxNzkyNTI2ODk4 }

    Tombstone:
      type: object
      description: A record that was removed, or that is no longer in your license. Delete it from your copy (ignore it if you never had it).
      required: [id, deleted, changed_at]
      properties:
        id: { type: integer }
        deleted: { type: boolean, const: true }
        changed_at: { type: string, format: date-time }

    DateStatus:
      type: [string, 'null']
      enum: [confirmed, estimated, copied, not_found, check, null]
      description: |
        How sure we are of a date. `confirmed`: published by the organizer and checked. `estimated`: projected
        from earlier editions' lead times. `copied`: carried forward from the previous edition, not yet
        confirmed. `not_found`: looked for and not published yet. `check`: under review. `null`: no date.

    Tag:
      type: object
      properties:
        id: { type: integer }
        type: { type: string, example: Topic Area }
        name: { type: string, example: Breast Cancer }
        category: { type: [string, 'null'] }

    Organization:
      type: object
      properties:
        id: { type: integer }
        name: { type: [string, 'null'], example: American Association for Cancer Research }
        abbv: { type: [string, 'null'], example: AACR }
        type: { type: [string, 'null'], example: Professional Society }
        url: { type: [string, 'null'] }

    Metric:
      type: [object, 'null']
      properties:
        value: { type: [number, 'null'] }
        year: { type: [integer, 'null'] }

    WeeksRange:
      type: [object, 'null']
      description: |
        A time in weeks, as published by the journal. `"3 - 4"` → min 3, max 4. `"6"` → min 6, max 6.
        `">= 3 - 4"` (at least) → min 3, max null. `"Will Not Disclose"` / `"N/A"` → min and max null, text kept.
      properties:
        min_weeks: { type: [integer, 'null'] }
        max_weeks: { type: [integer, 'null'] }
        text: { type: string, example: '3 - 4' }

    Conference:
      type: object
      required: [id, name, created_on, last_updated_on]
      # A real record, produced by the API itself (shortened: long texts are cut, 3 tags shown).
      example:
        id: 9995
        import_id: null
        name: "AACR - American Association for Cancer Research - Annual Meeting"
        name_abbv: "AACR"
        year: 2027
        type: "Tier 1"
        status: null
        is_active: true
        frequency: null
        previous_edition_id: 7562
        url: "https://www.aacr.org/meeting/aacr-annual-meeting-2027/"
        description: "The AACR Annual Meeting is the focal point of the cancer research community, where scientists, clinicians, other health care professionals…"
        notes: null
        primary_language: "English"
        geographic_scope: "International"
        social:
          x_handle: null
          linkedin: null
        start_date: "2027-04-02"
        end_date: "2027-04-07"
        start_date_status: "confirmed"
        dates_are_estimated: false
        dates_checked_on: null
        venue_name: "Orange County Convention Center"
        city: "Orlando"
        state: null
        country: "United States"
        est_attendees: 19500
        next_meeting: null
        accepts_abstracts: null
        abstract_open_date: "2025-09-15"
        abstract_due_date: "2026-11-10"
        abstract_due_date_status: "confirmed"
        abstract_notification_date: null
        abstract_published_online_date: null
        late_breaking_open_date: null
        late_breaking_due_date: "2027-01-06"
        late_breaking_due_date_status: "confirmed"
        late_breaking_notification_date: null
        late_breaking_published_online_date: null
        abstract_submission_url: null
        submission_guidelines: "https://www.aacr.org/meeting/aacr-annual-meeting-2026/abstracts/"
        prior_publication_policy: "Authors who submit abstracts must confirm that they have not previously published these data, that they have not previously presented them…"
        abstract_journal: "Cancer Research"
        formats:
          oral: true
          poster: true
          eposter: true
          video: false
        membership_required_to_attend: "No"
        membership_required_to_submit: "Yes"
        therapy_areas:
          - "Oncology"
        tags:
          - id: 7398
            type: "Attendee Geography"
            name: "Asia"
            category: null
            score: 6.0
          - id: 7399
            type: "Attendee Geography"
            name: "Europe"
            category: null
            score: 9.0
          - id: 7400
            type: "Attendee Geography"
            name: "Japan"
            category: null
            score: 0.0
        organizations:
          - id: 39932
            name: "American Association for Cancer Research (AACR)"
            abbv: "AACR"
            type: null
            url: "https://www.aacr.org"
        created_on: "2026-05-13T16:45:39Z"
        last_updated_on: "2026-10-20T18:43:54Z"
      properties:
        id: { type: integer, description: Stable record id. Each yearly edition of a meeting has its own., example: 9995 }
        import_id: { type: [integer, 'null'] }
        name: { type: string, description: '"Abbreviation - Organization - Event".', example: AACR - American Association for Cancer Research - Annual Meeting }
        name_abbv: { type: [string, 'null'], example: AACR }
        year: { type: [integer, 'null'], example: 2027 }
        type: { type: [string, 'null'], description: Free text as stored. }
        status: { type: [string, 'null'] }
        is_active: { type: [boolean, 'null'] }
        frequency: { type: [string, 'null'], description: 'When not annual: Biennial, Triennial, Quadrennial, Quinquennial.' }
        previous_edition_id: { type: [integer, 'null'], description: The same meeting's previous edition, when it is in your license. }
        url: { type: [string, 'null'] }
        description: { type: [string, 'null'] }
        notes: { type: [string, 'null'] }
        primary_language: { type: [string, 'null'], example: English }
        geographic_scope: { type: [string, 'null'], example: International }
        social:
          type: object
          properties:
            x_handle: { type: [string, 'null'] }
            linkedin: { type: [string, 'null'] }
        start_date: { type: [string, 'null'], format: date, example: '2027-04-02' }
        end_date: { type: [string, 'null'], format: date, example: '2027-04-07' }
        start_date_status: { $ref: '#/components/schemas/DateStatus' }
        dates_are_estimated: { type: [boolean, 'null'], description: Older flag; `start_date_status` is more precise. }
        dates_checked_on: { type: [string, 'null'], format: date, description: When we last checked the dates against the official source. }
        venue_name: { type: [string, 'null'] }
        city: { type: [string, 'null'], example: Orlando }
        state: { type: [string, 'null'] }
        country: { type: [string, 'null'], example: United States }
        est_attendees: { type: [integer, 'null'], example: 19500 }
        next_meeting: { type: [string, 'null'] }
        accepts_abstracts: { type: [string, 'null'], enum: ['Yes', 'No', 'TBD', null] }
        abstract_open_date: { type: [string, 'null'], format: date }
        abstract_due_date: { type: [string, 'null'], format: date, example: '2026-11-10' }
        abstract_due_date_status: { $ref: '#/components/schemas/DateStatus' }
        abstract_notification_date: { type: [string, 'null'], format: date }
        abstract_published_online_date: { type: [string, 'null'], format: date }
        late_breaking_open_date: { type: [string, 'null'], format: date }
        late_breaking_due_date: { type: [string, 'null'], format: date }
        late_breaking_due_date_status: { $ref: '#/components/schemas/DateStatus' }
        late_breaking_notification_date: { type: [string, 'null'], format: date }
        late_breaking_published_online_date: { type: [string, 'null'], format: date }
        abstract_submission_url: { type: [string, 'null'] }
        submission_guidelines: { type: [string, 'null'] }
        prior_publication_policy: { type: [string, 'null'] }
        abstract_journal: { type: [string, 'null'], description: Journal or supplement where accepted abstracts are published. }
        formats:
          type: object
          properties:
            oral: { type: [boolean, 'null'] }
            poster: { type: [boolean, 'null'] }
            eposter: { type: [boolean, 'null'] }
            video: { type: [boolean, 'null'] }
        membership_required_to_attend: { type: [string, 'null'], enum: ['Yes', 'No', null] }
        membership_required_to_submit: { type: [string, 'null'], enum: ['Yes', 'No', null] }
        therapy_areas: { type: array, items: { type: string }, example: [Oncology] }
        tags:
          type: array
          items:
            allOf:
              - $ref: '#/components/schemas/Tag'
              - type: object
                properties:
                  score: { type: [number, 'null'], description: Relevance score, where set. }
        organizations: { type: array, items: { $ref: '#/components/schemas/Organization' } }
        created_on: { type: string, format: date-time }
        last_updated_on: { type: string, format: date-time }

    Journal:
      type: object
      required: [id, name, created_on, last_updated_on]
      # A real record, produced by the API itself (shortened: long texts are cut, 3 tags shown).
      example:
        id: 489
        import_id: 20488
        name: "JAMA Oncology"
        name_abbv: "JAMA Oncol"
        type: "Tier 2"
        issn: "2374-2437"
        eissn: "2374-2445"
        url: "https://jamanetwork.com/journals/jamaoncology"
        scopus_url: "https://www.scopus.com/source/sourceInfo.url?sourceId=21100420583"
        description: "JAMA ONCOLOGY is committed to publishing influential original research, opinions, and reviews that advance the science of oncology and…"
        notes: "2025-Oct-23 03:51:32 PM EDT - CiteScore, SJR, SNIP, and ScopusURL updated."
        publisher: "American Medical Association"
        publisher_country: "United States"
        impact_factor: 33.006
        citescore:
          value: 32.6
          year: 2024
        sjr:
          value: 8.377
          year: 2024
        snip:
          value: 5.054
          year: 2024
        is_online: true
        is_print: true
        issues_per_year: 12
        circulation: 14096
        circulation_comment: "Circulation based on print.\nOnline statistics: Average annual visits - 2,500,000; Average annual views and downloads per - 4,300,000…"
        editorial_board_url: "https://jamanetwork.com/journals/jamaoncology/fullarticle/2800783"
        author_instructions_url: "https://jamanetwork.com/journals/jamaoncology/pages/instructions-for-authors"
        open_access: "Optional"
        open_access_comment: "Authors of research articles may elect to publish open access with a CC-BY or CC-BY-NC-ND license on the day of publication. The CC-BY…"
        author_charges: "Open access charges: All open access research articles that are accepted for publication will be made immediate free access on the journal…"
        peer_reviewed: true
        peer_review_comment: "All submitted manuscripts are reviewed initially by one of the editors. Manuscripts with insufficient priority for publication are rejected…"
        rapid_publication: null
        accelerated_options: "Online before print: New Online articles are published online in advance of print."
        acceptance_rate_pct: 8.0
        rejection_rate_pct: 92.0
        timelines:
          online_submit_to_accept:
            min_weeks: 6
            max_weeks: 7
            text: "6 - 7"
          online_accept_to_publish:
            min_weeks: 1
            max_weeks: 1
            text: "1"
          online_submit_to_publish:
            min_weeks: 7
            max_weeks: 8
            text: "7 - 8"
          print_submit_to_accept:
            min_weeks: 6
            max_weeks: 7
            text: "6 - 7"
          print_accept_to_publish:
            min_weeks: null
            max_weeks: null
            text: "Will Not Disclose"
          print_submit_to_publish:
            min_weeks: 6
            max_weeks: null
            text: ">= 6 - 7"
        tags:
          - id: 6521
            type: "Audience"
            name: "Hematologists"
            category: null
          - id: 6629
            type: "Audience"
            name: "Oncologists"
            category: null
          - id: 6630
            type: "Audience"
            name: "Oncologists; Clinical"
            category: null
        organizations:
          - id: 41972
            name: "American Medical Association"
            abbv: "AMA"
            type: null
            url: "https://www.ama-assn.org/"
        created_on: "2023-06-09T18:16:33Z"
        last_updated_on: "2025-11-04T18:56:04Z"
      properties:
        id: { type: integer, example: 489 }
        import_id: { type: [integer, 'null'] }
        name: { type: string, example: JAMA Oncology }
        name_abbv: { type: [string, 'null'], example: JAMA Oncol }
        type: { type: [string, 'null'] }
        issn: { type: [string, 'null'], example: 2374-2437 }
        eissn: { type: [string, 'null'], example: 2374-2445 }
        url: { type: [string, 'null'] }
        scopus_url: { type: [string, 'null'] }
        description: { type: [string, 'null'] }
        notes: { type: [string, 'null'] }
        publisher: { type: [string, 'null'], example: American Medical Association }
        publisher_country: { type: [string, 'null'] }
        impact_factor: { type: [number, 'null'], example: 33.006 }
        citescore: { $ref: '#/components/schemas/Metric' }
        sjr: { $ref: '#/components/schemas/Metric' }
        snip: { $ref: '#/components/schemas/Metric' }
        is_online: { type: [boolean, 'null'] }
        is_print: { type: [boolean, 'null'] }
        issues_per_year: { type: [integer, 'null'], example: 12 }
        circulation: { type: [integer, 'null'] }
        circulation_comment: { type: [string, 'null'] }
        editorial_board_url: { type: [string, 'null'] }
        author_instructions_url: { type: [string, 'null'] }
        open_access: { type: [string, 'null'], example: Optional }
        open_access_comment: { type: [string, 'null'] }
        author_charges: { type: [string, 'null'] }
        peer_reviewed: { type: [boolean, 'null'] }
        peer_review_comment: { type: [string, 'null'] }
        rapid_publication: { type: [boolean, 'null'] }
        accelerated_options: { type: [string, 'null'] }
        acceptance_rate_pct: { type: [number, 'null'], example: 8 }
        rejection_rate_pct: { type: [number, 'null'], example: 92 }
        timelines:
          type: object
          properties:
            online_submit_to_accept: { $ref: '#/components/schemas/WeeksRange' }
            online_accept_to_publish: { $ref: '#/components/schemas/WeeksRange' }
            online_submit_to_publish: { $ref: '#/components/schemas/WeeksRange' }
            print_submit_to_accept: { $ref: '#/components/schemas/WeeksRange' }
            print_accept_to_publish: { $ref: '#/components/schemas/WeeksRange' }
            print_submit_to_publish: { $ref: '#/components/schemas/WeeksRange' }
        tags: { type: array, items: { $ref: '#/components/schemas/Tag' } }
        organizations: { type: array, items: { $ref: '#/components/schemas/Organization' } }
        created_on: { type: string, format: date-time }
        last_updated_on: { type: string, format: date-time }
