The SelfDecode B2B list endpoints covered in this guide return results one page at a time. This guide explains the parameters they share, then lists each endpoint's filters, default order and recommended call pattern. All examples use the production base URL https://selfdecode.com/service/b2b-integrations and assume the usual Authorization header.
Every parameter listed here also appears on the endpoint's page in the API reference, which is the source of truth for allowed values.
Common parameters
These parameters behave the same way on every list endpoint.
| Parameter | Description |
|---|---|
page | Page number, starting at 1. Defaults to 1. |
page_size | Number of items per page. Defaults to 100 and cannot exceed 100. A larger value is not rejected; it is reduced to 100. |
sort | Sort order, where the endpoint supports it. Prefix a field with - for descending, e.g. sort=-created_at. |
created_at_gte, created_at_lte | ISO 8601 datetimes that restrict results to items created on or after / on or before that moment. Available on order, every job endpoint and genome-file. |
Parameters that accept several values take them comma-separated, e.g. profile_id=<uuid1>,<uuid2>.
The response body is a plain JSON array of up to page_size items. There is no total count or next link: if a page returns fewer items than page_size, or an empty array, it is the last page. When the total is an exact multiple of page_size, the page after the last full one is empty. Pages are only meaningful relative to an order, so every endpoint returns results in a fixed, stable order (stated per endpoint below), and paging through it never repeats or skips an item. A page that is not a positive integer is rejected with a 4xx error rather than treated as page 1.
General guidance
Ask for what you need rather than downloading everything and filtering on your side. Most integrations only need the latest few items, or the items for one profile, or the items created since their last sync, and each of those is a single cheap request. Pulling a full list is slow for you and costly for us, and responses are now capped at 100 items per page, so a "get everything" loop needs many requests to return data you will mostly discard.
In practice that means three things. To get the latest few items, rely on the newest-first order with a small page_size. To look up a single item you already know, retrieve it by ID (GET /<endpoint>/<id>/) instead of listing. For incremental sync, filter by created_at_gte with the time of your last successful sync. To react to job status changes, subscribe to webhooks instead of polling a list.
Orders
GET /order/ returns your DNA kit orders, newest first (-created_at). It accepts sort with the values created_at and -created_at. Its filters are id and profile_id (both comma-separated UUIDs) and created_at_gte / created_at_lte.
To get the 10 most recent orders, call GET /order/?page_size=10. For orders placed since your last sync, call GET /order/?created_at_gte=2026-09-01T00:00:00Z.
Avoid listing every order to find one or to check its status. Use GET /order/<id>/, or filter with id=<uuid>.
Job endpoints
Job endpoints track the asynchronous jobs you create (reports, PDFs, file processing, and so on). They all share the same parameters and return results newest first (-created_at). They accept sort with the values created_at and -created_at.
Every job endpoint accepts profile_id (comma-separated UUIDs), status (a single value from that endpoint's status list, see the API reference), and created_at_gte / created_at_lte. The endpoints below add the extra filters shown.
| Endpoint | Extra filters |
|---|---|
report-job | report_id (comma-separated UUIDs) |
dna-kit-job | sample_id (comma-separated) |
carrier-status-job | report_id (comma-separated UUIDs) |
ancestry-job | ancestry_type (ancestry or mtdna) |
snp-file-job | snp_list (a single SNP list ID) |
custom-snp-report-job | user_collection_id (comma-separated UUIDs) |
bio-chemistry-job, hair-loss-job, genome-file-job, clinical-overview-job, pgx-job, lab-ocr-job, longevity-screener-job, health-overview-job, acmg-job, action-plan-job, rare-variants-job, wgs-pgx-job, hla-job, functional-variants-job, pdf-job, lab-pdf-job | None |
To get the latest jobs for a profile, call GET /report-job/?profile_id=<uuid>&page_size=10. To list only failed jobs since yesterday, call GET /report-job/?status=failed_report_gen&created_at_gte=2026-09-24T00:00:00Z. For the latest report job of a specific profile and report, report-job has a dedicated lookup: GET /report-job/profile/<profile_id>/report/<report_id>/.
Avoid polling a full job list to track progress. Your account can hold hundreds of thousands of jobs, and re-reading them on every poll is the single most expensive pattern we see. Retrieve the job you are waiting on by ID, filter by profile_id and status, or use webhooks.
Profile data endpoints
These endpoints return data belonging to one of your profiles. Always pass profile_id; where it is required, the request fails without it.
| Endpoint | Filters | Sorting | Default order |
|---|---|---|---|
genome-file | profile_id (comma-separated), status (comma-separated), created_at_gte, created_at_lte | sort: created_at, -created_at | Newest first |
result | profile_id (required), marker_id, color, most_recent_only, out_of_optimal | sort: date_taken, marker_name, risk (prefix with - for descending) | Newest first |
composed-marker-result | profile_id (required), marker_id (required) | sort: date_taken, -date_taken (orders the marker's results) | Newest result first. Returns the single requested marker, so it is not paginated. |
profile-report-outcome | profile_id (required), report_id (comma-separated), report_type (standard, trait, gene) | sort: health_issue_impact, -health_issue_impact | Stable |
profile-lifestyle-assessment-summary | profile_id (required), lifestyle_assessment_id (comma-separated) | sort: created_at, -created_at | Newest first |
answer | profile_id (required), questionnaire_id (required) | None | Question order |
For the latest value of each marker, call GET /result/?profile_id=<uuid>&most_recent_only=true rather than fetching the full result history. For only the out-of-range markers, add out_of_optimal=true. For a profile's most impactful report outcomes, call GET /profile-report-outcome/?profile_id=<uuid>&sort=-health_issue_impact&page_size=10.
Avoid fetching everything for a profile and filtering on your side. A single profile can hold thousands of results and report outcomes, so use most_recent_only, report_id or marker_id to narrow the request.
Catalog endpoints
These endpoints return SelfDecode catalog content (reports, recommendations, markers, articles, and so on) that is the same for all profiles. Catalog content changes rarely, so fetch what you need and cache it rather than re-reading the whole catalog on each request.
| Endpoint | Filters | Sorting | Default order |
|---|---|---|---|
report-summary | profile_id, name, area, report_type, listing_type, visible_to_genders, is_deprecated | sort: created_at, -created_at | Newest first |
recommendation, recommendation-summary, recommendation-id | id, related_id, recommendation_type, q (search). At least one filter or sort is required. | sort: created_at, updated_at, impact (each with -). impact requires profile_id. | Newest first |
marker-summary | id, report_id, area, search | None | Newest first |
article-summary | slug, q (search), genes, area, related_report_id | sort: title, publish_date (each with -) | Newest published first |
lifestyle-assessment-summary | visible_to_genders, area, disease_name | None | Stable |
carrier-status-report-summary | id | None | Stable |
meal | id, category, profile_id | None | Newest first |
intake-form | title, slug, archived | sort: title, updated_at (each with -) | Form type, then most recently updated |
Filters listed as plural (id, area, report_type, related_id, genes, and so on) accept comma-separated values. "Stable" means results come back in a fixed order that is safe to page through, but not by date.
On the recommendation endpoints, profile_id is not a filter: it does not restrict results to a profile and does not satisfy the "at least one filter" rule on its own. It only supplies the profile used to rank results when sorting by impact, so pair it with sort=impact or sort=-impact.
To get a profile's top 10 recommendations, call GET /recommendation-id/?profile_id=<uuid>&sort=-impact&page_size=10. For the reports in one area, call GET /report-summary/?area=<area>. For the latest articles about a gene, call GET /article-summary/?genes=MTHFR&sort=-publish_date&page_size=10.
Avoid pulling the whole report, recommendation or article catalog on every user request. Cache catalog data on your side and refresh it periodically, filtering by id or created_at where supported.
Endpoints that deviate from the standard
sort is the sort parameter on every endpoint. order also accepts ordering, and result and intake-form also accept order_by, with the same values. Both names are deprecated and will be removed in a future release, so switch to sort. If a request sends both, sort takes precedence.
profile_id accepts comma-separated values on job endpoints, genome-file and report-summary, but only a single UUID on result, composed-marker-result, profile-report-outcome, profile-lifestyle-assessment-summary and answer. On snp-file-job the filter is named snp_list rather than snp_list_id and takes a single ID.
article-summary, lifestyle-assessment-summary and carrier-status-report-summary items have no created_at, so the created_at_gte / created_at_lte filters and newest-first order do not apply to them. intake-form is ordered by form type and last update, not by creation date.
composed-marker-result returns the single marker named by marker_id, with that profile's results for it nested inside, so it is not paginated.
open-target-snp-summary is paginated differently: its maximum page_size is 20, and its response is wrapped as {count, next, previous, results} rather than being a plain array.
