From 7b785126edbfabb98f7bf4ac49e8b66948b3c7ab Mon Sep 17 00:00:00 2001 From: Jonathan Payne Date: Wed, 7 Oct 2026 12:53:28 -0400 Subject: [PATCH 1/3] closes OpenConceptLab/ocl_issues#2881 | Export API and Subscriptions: POST/DELETE responses, download steps - Overview: is_processing turning False doesn't mean an export exists. - POST: 208 while exporting, 409 only for an already-queued job, 303 with a URL header (or 204 with noRedirect=true) when the export exists, and force=true to create it again. - DELETE: Delete caption, 204 No Content / 404 Not Found, 403 for non-admins. - Subscriptions: the export GET redirects (Location) to an expiring signed URL for a zip, not an exportURL header for a single-use tar; convert the filename's lastUpdated to ISO 8601 for updatedSince. Co-Authored-By: Claude Opus 5.5 --- docs/source/oclapi/apireference/exportapi.md | 39 +++++++++++++------ .../apireference/other/subscriptions.md | 10 ++--- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/docs/source/oclapi/apireference/exportapi.md b/docs/source/oclapi/apireference/exportapi.md index 48354ce..a71d24e 100644 --- a/docs/source/oclapi/apireference/exportapi.md +++ b/docs/source/oclapi/apireference/exportapi.md @@ -1,7 +1,7 @@ # Export API ## Overview -The API provides an `export` endpoint for creating, fetching, and deleting a cached export of repository version. Exports are automatically generated upon creation of a new source or collection version and cached, so requesting an export is a quick operation even for a large repository. The recommended method for determining if an export is available after creating a new repository version is by checking the status code of a `HEAD` request to the export, eg `HEAD /[:ownerType/]:owner/:repoType/:repo/:repoVersion/export/`. The status code is `302` if the export is ready for download, `208` if it is still being created, or `204` if there is no export. Don't follow the redirect when checking availability (for example, use `curl -I` without `-L`): the signed URL is valid only for `GET`. Note that the old method of determining when an export is available is by checking the `is_processing` flag of the repository version, which is set to `False` after processing of the new repository version is complete. This method is still correct, but if you only need an export and not a fully processed repository version, then the Export API method is more performant as creating and uploading an export is a much faster operation. +The API provides an `export` endpoint for creating, fetching, and deleting a cached export of repository version. Exports are automatically generated upon creation of a new source or collection version and cached, so requesting an export is a quick operation even for a large repository. The recommended method for determining if an export is available after creating a new repository version is by checking the status code of a `HEAD` request to the export, eg `HEAD /[:ownerType/]:owner/:repoType/:repo/:repoVersion/export/`. The status code is `302` if the export is ready for download, `208` if it is still being created, or `204` if there is no export. Don't follow the redirect when checking availability (for example, use `curl -I` without `-L`): the signed URL is valid only for `GET`. The repository version's `is_processing` flag is not a reliable signal for this: it turns `False` when processing of the new version ends, even if creating the export failed. Use the Export API to find out whether an export exists. It is also faster, since creating and uploading an export takes much less time than fully processing a version. The Export API enables a client to manage cached exports manually for special situations, such as triggering the creation of a new export that did not get cached correctly. The filename of the export contains the export's `lastUpdated` timestamp, which can be used to fetch the diff between an export and the current state of a source or collection. A `GET` request to the export endpoint redirects to a signed download URL, and the download carries the filename in its `Content-Disposition` response header. @@ -102,9 +102,10 @@ Status: 404 Not Found ## Create an export of a repository version -* Create an export file for the specified repository version; if it already exists, no action is taken +* Create an export file for the specified repository version. If one already exists, no action is taken unless `force=true` is passed, which creates it again. ``` POST /[:ownerType/]:owner/:repoType/:repo/:repoVersion/export/ +POST /[:ownerType/]:owner/:repoType/:repo/:repoVersion/export/?force=true ``` * Notes * `:repoVersion` is required. `HEAD` exports are available only to staff, superusers and the repository's owner (the owning user, or members of the owning organization); other users who can see the repository get `405 Not Allowed`. @@ -117,17 +118,27 @@ POST /orgs/CIEL/sources/CIEL/v2.2/export/ ``` ### Response -* If no export file already exists and processing is initiated: +* If no export file already exists (or `force=true` was passed) and processing is initiated: ``` Status: 202 Accepted ``` -* If an export file is currently being processed: +* If an export file is currently being created: +``` +Status: 208 Already Reported +``` +* If the same export job is already queued: ``` Status: 409 Conflict ``` -* If the export file already exists (use GET to download it): +* If the export file already exists, the response points to the export endpoint (use GET there to download it): ``` -Status: 200 OK +Status: 303 See Other +Response Header: +URL: /[:ownerType/]:owner/:repoType/:repo/:repoVersion/export/ +``` +* If the export file already exists and `noRedirect=true` was passed: +``` +Status: 204 No Content ``` * If the request is otherwise invalid - return the appropriate error code @@ -140,22 +151,26 @@ DELETE /[:ownerType/]:owner/:repoType/:repo/:repoVersion/export/ ``` * Notes * `HEAD` exports are available only to staff, superusers and the repository's owner (the owning user, or members of the owning organization); other users who can see the repository get `405 Not Allowed`. - * The passed authorization token must have administrative access to the repository in order to delete the export file + * The passed authorization token must have administrative access to the repository (staff, superusers or the repository's owner) in order to delete the export file; otherwise the API returns `403 Forbidden`. ### Example -* Create the export file for v2.2 of the CIEL source +* Delete the export file for v2.2 of the CIEL source ``` DELETE /orgs/CIEL/sources/CIEL/v2.2/export/ ``` ### Response -* If the file exists, it is deleted with 204 +* If the file exists, it is deleted: +``` +Status: 204 No Content +``` +* If the file does NOT exist: ``` -Status: 204 Success +Status: 404 Not Found ``` -* If the file does NOT exist +* If the user doesn't have administrative access to the repository: ``` -Status: 404 No Content +Status: 403 Forbidden ``` * If the request is otherwise invalid - return the appropriate error code diff --git a/docs/source/oclapi/apireference/other/subscriptions.md b/docs/source/oclapi/apireference/other/subscriptions.md index c5ea0c5..bf5971c 100644 --- a/docs/source/oclapi/apireference/other/subscriptions.md +++ b/docs/source/oclapi/apireference/other/subscriptions.md @@ -19,11 +19,11 @@ _For the first synchronization, fetch the most recent export of a **released** s ``` GET /orgs/[:org]/sources/[:source]/latest/ ``` -* Retrieve the filename of full export for the latest released source version, which is returned in the response header as the `exportURL` field. Note that if the export file does not already exist, is still being processed, or is invalid, the API returns a specific status or error code and the export can be retrieved later after an appropriate interval. Refer to [[Export-API]] for more info on this request. +* Request the full export of the latest released source version. If the export is ready, the API responds `302 Found` and its `Location` response header holds a signed URL to download the file. If the export file does not already exist or is still being created, the API returns a specific status code (`204` or `208`), and the export can be requested again after an appropriate interval. Refer to [[Export-API]] for more info on this request. ``` GET /orgs/[:org]/sources/[:source]/[:sourceVersion]/export/ ``` -* Download the export file from the `exportURL` (returns a compressed tar file of the JSON). Record the `lastUpdated` timestamp which is part of the filename. Refer to [[Export-API]] for more info on this request. Note that the `exportURL` is designed to be used only once and it expires after a set time limit -- to ensure that the `exportURL` works correctly, use it immediately and request another `exportURL` each time you want to download an export file. +* Download the export file from the signed URL (a zip file of the JSON); most HTTP clients follow the redirect automatically. Record the `lastUpdated` timestamp, which is the last part of the filename (e.g. `2026-09-30_123456`), and convert it to ISO 8601 (`2026-09-30T12:34:56Z`) for use with `updatedSince`. Refer to [[Export-API]] for more info on this request. Note that the signed URL expires after a set time limit -- use it immediately, and request the export again each time you want to download an export file. * Fetch all updates to the source that are more recent than the export using the `lastUpdated` timestamp (e.g. ISO 8601 timestamp (e.g. `2011-11-16T14:26:15Z`)). There are two methods for doing this: * Use a combination of the `limit` and `offset` parameters to iteratively retrieve results until fewer than `limit` results are returned. Note that the standard paging response headers (num_returned, num_found, offset, page, next_url, prev_url) are not supported at the source endpoint, and paging must be managed by the client. ``` @@ -46,9 +46,9 @@ GET /orgs/[:org]/sources/[:source]/?includeMappings=true&includeConcepts=true&in * A subscription client, such as the OpenMRS OCL Subscription module, should be configured with the source URL (e.g. `/orgs/CIEL/sources/CIEL/), an API token, and a synchronization schedule (e.g. weekly or monthly) * On the first synchronization: * Subscription client will request details on the most recent released source version: `GET /orgs/CIEL/sources/CIEL/latest/` - * Subscription client will request the export file URL of the most recent released source version, which will return the `exportURL` in the response header: `GET /orgs/CIEL/sources/CIEL/[:sourceVersion]/export/` + * Subscription client will request the export of the most recent released source version, which redirects to a signed download URL (the `Location` response header): `GET /orgs/CIEL/sources/CIEL/[:sourceVersion]/export/` * Note that if the export file is not ready, the subscription client will need to request the export file again after an appropriate interval - * Subscription client will download the export file from `exportURL`, which is returned as a compressed tar of the JSON results -- the `exportURL` can be discarded, as it is re-generated for each request + * Subscription client will download the export file from the signed URL, which is returned as a zip of the JSON results -- the signed URL can be discarded, as it is re-generated for each request * Subscription client will decompress the file and process the results * Subscription client may then request any changes to the source that occurred after the `lastUpdated` timestamp of the export file: `GET /orgs/CIEL/sources/CIEL/?includeConcepts=true&includeMappings=true&includeRetired=true&updatedSince=[:lastUpdated]` -- these results should be processed in the same manner as above; the `lastUpdated` date should be stored for subsequent synchronizations * On subsequent synchronizations: @@ -255,7 +255,7 @@ POST /orgs/MyOrg/sources/MySource/v1.0/export/
  • Make changes to concepts and mappings - these will be saved to source version `v1.1`
  • Fetch the export and changes made since the export
      -
    • Fetch the export - the request returns the URL to download the export file in the header attribute `exportUrl` +
    • Fetch the export - the request redirects (`302 Found`) to the URL of the export file, given in the `Location` header
       GET /orgs/MyOrg/sources/MySource/v1.0/export/
       
      From 0daea32858820196dd7037dccda636efc9faa715 Mon Sep 17 00:00:00 2001 From: Jonathan Payne Date: Wed, 7 Oct 2026 13:00:42 -0400 Subject: [PATCH 2/3] OpenConceptLab/ocl_issues#2881 | Export docs: POST precedence, 204 needs a POST, DELETE limit Addresses Codex pass 1: order of 208/force/noRedirect on POST; a 204 on GET means no export, so subscription clients POST to create one; DELETE removes only the export under the current lastUpdated; drop the unsupported speed claim. Co-Authored-By: Claude Opus 5.5 --- docs/source/oclapi/apireference/exportapi.md | 12 +++++++----- .../oclapi/apireference/other/subscriptions.md | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/source/oclapi/apireference/exportapi.md b/docs/source/oclapi/apireference/exportapi.md index a71d24e..8c19de1 100644 --- a/docs/source/oclapi/apireference/exportapi.md +++ b/docs/source/oclapi/apireference/exportapi.md @@ -1,7 +1,7 @@ # Export API ## Overview -The API provides an `export` endpoint for creating, fetching, and deleting a cached export of repository version. Exports are automatically generated upon creation of a new source or collection version and cached, so requesting an export is a quick operation even for a large repository. The recommended method for determining if an export is available after creating a new repository version is by checking the status code of a `HEAD` request to the export, eg `HEAD /[:ownerType/]:owner/:repoType/:repo/:repoVersion/export/`. The status code is `302` if the export is ready for download, `208` if it is still being created, or `204` if there is no export. Don't follow the redirect when checking availability (for example, use `curl -I` without `-L`): the signed URL is valid only for `GET`. The repository version's `is_processing` flag is not a reliable signal for this: it turns `False` when processing of the new version ends, even if creating the export failed. Use the Export API to find out whether an export exists. It is also faster, since creating and uploading an export takes much less time than fully processing a version. +The API provides an `export` endpoint for creating, fetching, and deleting a cached export of repository version. Exports are automatically generated upon creation of a new source or collection version and cached, so requesting an export is a quick operation even for a large repository. The recommended method for determining if an export is available after creating a new repository version is by checking the status code of a `HEAD` request to the export, eg `HEAD /[:ownerType/]:owner/:repoType/:repo/:repoVersion/export/`. The status code is `302` if the export is ready for download, `208` if it is still being created, or `204` if there is no export. Don't follow the redirect when checking availability (for example, use `curl -I` without `-L`): the signed URL is valid only for `GET`. The repository version's `is_processing` flag is not a reliable signal for this: it turns `False` when processing of the new version ends, even if creating the export failed. Use the Export API to find out whether an export exists. The Export API enables a client to manage cached exports manually for special situations, such as triggering the creation of a new export that did not get cached correctly. The filename of the export contains the export's `lastUpdated` timestamp, which can be used to fetch the diff between an export and the current state of a source or collection. A `GET` request to the export endpoint redirects to a signed download URL, and the download carries the filename in its `Content-Disposition` response header. @@ -118,14 +118,15 @@ POST /orgs/CIEL/sources/CIEL/v2.2/export/ ``` ### Response -* If no export file already exists (or `force=true` was passed) and processing is initiated: -``` -Status: 202 Accepted -``` +* The API checks these cases in order: an export being created (`208`) takes precedence over `force` and `noRedirect`, and `force=true` takes precedence over `noRedirect`. * If an export file is currently being created: ``` Status: 208 Already Reported ``` +* If no export file already exists (or `force=true` was passed) and processing is initiated: +``` +Status: 202 Accepted +``` * If the same export job is already queued: ``` Status: 409 Conflict @@ -152,6 +153,7 @@ DELETE /[:ownerType/]:owner/:repoType/:repo/:repoVersion/export/ * Notes * `HEAD` exports are available only to staff, superusers and the repository's owner (the owning user, or members of the owning organization); other users who can see the repository get `405 Not Allowed`. * The passed authorization token must have administrative access to the repository (staff, superusers or the repository's owner) in order to delete the export file; otherwise the API returns `403 Forbidden`. + * DELETE removes the export cached under the version's current `lastUpdated`. An export of the same version cached under an earlier timestamp isn't removed, and GET may still return it. ### Example * Delete the export file for v2.2 of the CIEL source diff --git a/docs/source/oclapi/apireference/other/subscriptions.md b/docs/source/oclapi/apireference/other/subscriptions.md index bf5971c..cfea4f2 100644 --- a/docs/source/oclapi/apireference/other/subscriptions.md +++ b/docs/source/oclapi/apireference/other/subscriptions.md @@ -19,7 +19,7 @@ _For the first synchronization, fetch the most recent export of a **released** s ``` GET /orgs/[:org]/sources/[:source]/latest/ ``` -* Request the full export of the latest released source version. If the export is ready, the API responds `302 Found` and its `Location` response header holds a signed URL to download the file. If the export file does not already exist or is still being created, the API returns a specific status code (`204` or `208`), and the export can be requested again after an appropriate interval. Refer to [[Export-API]] for more info on this request. +* Request the full export of the latest released source version. If the export is ready, the API responds `302 Found` and its `Location` response header holds a signed URL to download the file. If the export is still being created, the API returns `208 Already Reported`; request it again after an appropriate interval. If there is no export, the API returns `204 No Content`; create one with `POST /orgs/[:org]/sources/[:source]/[:sourceVersion]/export/`, then request it again once it is ready. Refer to [[Export-API]] for more info on this request. ``` GET /orgs/[:org]/sources/[:source]/[:sourceVersion]/export/ ``` @@ -47,7 +47,7 @@ GET /orgs/[:org]/sources/[:source]/?includeMappings=true&includeConcepts=true&in * On the first synchronization: * Subscription client will request details on the most recent released source version: `GET /orgs/CIEL/sources/CIEL/latest/` * Subscription client will request the export of the most recent released source version, which redirects to a signed download URL (the `Location` response header): `GET /orgs/CIEL/sources/CIEL/[:sourceVersion]/export/` - * Note that if the export file is not ready, the subscription client will need to request the export file again after an appropriate interval + * Note that if the export file is not ready (`208`), the subscription client will need to request the export file again after an appropriate interval; if there is no export (`204`), it must first create one with a `POST` to the same URL * Subscription client will download the export file from the signed URL, which is returned as a zip of the JSON results -- the signed URL can be discarded, as it is re-generated for each request * Subscription client will decompress the file and process the results * Subscription client may then request any changes to the source that occurred after the `lastUpdated` timestamp of the export file: `GET /orgs/CIEL/sources/CIEL/?includeConcepts=true&includeMappings=true&includeRetired=true&updatedSince=[:lastUpdated]` -- these results should be processed in the same manner as above; the `lastUpdated` date should be stored for subsequent synchronizations From 5cab8e90b4328c6ccd2fc063c2d22e1c47369157 Mon Sep 17 00:00:00 2001 From: Jonathan Payne Date: Wed, 7 Oct 2026 13:05:41 -0400 Subject: [PATCH 3/3] OpenConceptLab/ocl_issues#2881 | Export API: the POST 303 URL header omits HEAD/ for HEAD exports Addresses Codex pass 2 on ocl-docs#69. Co-Authored-By: Claude Opus 5.5 --- docs/source/oclapi/apireference/exportapi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/oclapi/apireference/exportapi.md b/docs/source/oclapi/apireference/exportapi.md index 8c19de1..1193922 100644 --- a/docs/source/oclapi/apireference/exportapi.md +++ b/docs/source/oclapi/apireference/exportapi.md @@ -131,7 +131,7 @@ Status: 202 Accepted ``` Status: 409 Conflict ``` -* If the export file already exists, the response points to the export endpoint (use GET there to download it): +* If the export file already exists, the response points to the export endpoint; download it with a GET to the same export URL you posted to. For a `HEAD` export, the `URL` header leaves out `HEAD/`, so use the URL you posted to rather than the header. ``` Status: 303 See Other Response Header: