agek-tileserverdata (0.1.0)

Download OpenAPI specification:

Overview

Converts SLPK/TPKX/IFC/RVT/LAS/raster/mesh packages uploaded to an S3 input bucket into i3sREST or tile format in an output bucket, and (optionally) publishes the result as an ArcGIS Enterprise service item. Built for both the browser SPA and machine-to-machine callers (FME and similar) — both use the exact same endpoints and header scheme below.

Authentication

Every endpoint except the health probes requires:

Header Purpose
X-Client-Id Identifies the calling client — resolves the customer's S3/portal configuration
X-Client-Secret Authenticates the call for that client
X-Portal-Token The ArcGIS Portal user's access token. Required whenever the call publishes (auto_publish, POST /publish, register_source_item, or a non-private sharing_level) or lists job history (GET /jobs); not required for an upload-only call

X-Client-Id/X-Client-Secret are issued by Geodata per customer (a dedicated entry in the customer's client config — a machine-to-machine caller like FME gets its own, separate from the browser SPA's) — ask Geodata to provision one, it isn't self-service in Portal. X-Portal-Token is self-service — see the next section.

Getting X-Portal-Token for a machine-to-machine caller (FME etc.)

A browser SPA gets this from its OAuth2 login. A script/scheduled job (FME or similar) has no browser, so it needs one of these instead — all three produce a value that's used exactly the same way, unchanged, as X-Portal-Token:

Option How to get it Lifetime Needs a named user?
generateToken POST {portal_url}/sharing/rest/generateToken 60–120 min (configurable up to the org's cap) Yes — a dedicated service account
ArcGIS API Key Create an API key credential in Portal (see steps below) No expiry (revocable) Yes — tied to one user
OAuth Credentials (client_credentials) POST {portal_url}/sharing/rest/oauth2/token Short-lived, fetch a new one whenever needed No — its own app registration in Portal

Setting up an ArcGIS API key in Portal

This is the recommended option for unattended production automation (no password to rotate, no login session to keep alive) — requires ArcGIS Enterprise 10.9.1+.

  1. Sign in to the Portal as the named user the integration should act as (needs publishing rights to whatever the integration will publish).
  2. Go to Content → New item → Developer credential (menu wording varies a little by Portal version — look for "API key" / "Developer credential" under Content or My Content if it isn't exactly there).
  3. Choose the privileges the key needs (at minimum: publish content) and, optionally, an expiration and HTTP referrer restriction.
  4. Create it and copy the key value it gives you — Portal shows it once.
  5. Send that value as X-Portal-Token on every API call. Store it the same way you'd store a password/secret — anyone with it can act as that user.

generateToken example (FME HTTP Caller, curl, or similar)

POST {portal_url}/sharing/rest/generateToken
Body: username=svc_fme&password=...&client=requestip&expiration=120&f=json
→ {"token": "<portal-token>", "expires": ...}

OAuth Credentials example (client_credentials grant)

POST {portal_url}/sharing/rest/oauth2/token
Body: client_id=<oauth_credentials_client_id>&client_secret=<oauth_credentials_client_secret>&grant_type=client_credentials&f=json
→ {"access_token": "<portal-token>", "expires_in": ...}

Long-running jobs and token expiry: extraction can take from minutes to a few hours for large files, but X-Portal-Token is captured once, at the moment you call /publish or /uploads/complete, and frozen for the whole job — there is no server-side refresh for a machine caller. If the token expires before extraction finishes, the publish step fails with an invalid-token error even though the file processed fine. Request an expiration (or API-key expiry) comfortably longer than your largest expected file's processing time, not just long enough to make the initial call. A browser SPA can instead pass an optional portal_refresh_token alongside auto_publish so the backend mints a fresh token right before publishing — that field is SPA-specific (a real OAuth refresh token isn't something generateToken/API-key/client_credentials produce) and, being a credential, must never be logged or displayed.

Choosing an upload path

Two independent upload mechanisms exist — pick per file size / integration:

  • Proxy (POST /upload) — the file streams through this API. Simple (one endpoint), but the whole file passes through the API pod and its self-imposed ingress timeout, so it doesn't suit very large files. See "Proxy upload" below.
  • Presigned (POST /uploads + friends) — the client PUTs bytes straight to S3; this API only ever signs URLs and never sees file bytes. Built for large files. See "Presigned direct-to-S3 upload" below.

Both eventually enqueue the exact same background extraction+publish job — only the path the bytes take to reach S3 differs.

Proxy upload: upload → extract → publish

Two ways to run the full pipeline over the proxy path — pick whichever shape suits the integration. Both end up enqueuing the exact same background job with the same parameters.

Option A — one call

POST /upload (multipart form) with auto_publish=true, service_name, and X-Portal-Token set does everything: streams the file to S3, then immediately enqueues extraction + publish. The response is a Server-Sent-Events stream:

data: {"status": "uploading", "bytes_uploaded": 1048576, "total": 52428800, "progress_pct": 2.0}
...
data: {"status": "uploaded", "input_key": "tileserver/input/model.slpk"}
data: {"status": "queued", "task_id": "abc123", "task_name": "process_slpk", "poll_url": "/tasks/abc123"}

Take task_id from the final queued event and poll GET /tasks/{task_id}.

Option B — two calls (upload, then publish separately)

Useful for confirming the upload succeeded before committing to a publish, letting a user pick the service name/sharing afterwards, or re-publishing an already-uploaded file.

  1. POST /upload with auto_publish omitted (defaults to false) — same SSE stream as above, but it stops after the uploaded event. Keep the input_key from that event.
  2. POST /publish with {"portal_url", "input_key", "service_name", ...} and X-Portal-Token — enqueues the same extraction+publish job. Responds immediately with a TaskModel (id / poll_url), as plain JSON rather than a stream.

Presigned direct-to-S3 upload (large files)

Four calls, three of them to this API and one straight to S3 — no file bytes ever pass through this API:

  1. POST /uploads{"portal_url", "filename", "size"}{"upload_id", "input_key", "part_size"}. Starts an S3 multipart upload and tells you the part size to slice the file into (currently 64 MiB).
  2. POST /uploads/parts{"portal_url", "input_key", "upload_id", "part_numbers": [1, 2, ...]} (up to 100 part numbers per call) → a presigned S3 URL per part number, each valid for 1 hour.
  3. PUT each file part directly to its presigned URL — not a call to this API at all. S3 returns an ETag header on each successful PUT; keep every (part_number, etag) pair. Re-sign a part (repeat step 2 for just that number) if its URL expires or the PUT fails and needs retrying past the 1-hour window. A browser client's CORS preflight for these PUTs is handled by the S3 bucket's CORS config, not this API.
  4. POST /uploads/complete{"portal_url", "input_key", "upload_id", "parts": [{"part_number", "etag"}, ...], "auto_publish", "service_name", ...} and (if publishing) X-Portal-Token — finalizes the S3 multipart upload and, if auto_publish=true, enqueues the same extraction+publish job as the proxy path. Responds immediately with {"input_key", "task"} (JSON, task is null unless auto_publish=true).

Abandoning an upload partway through: DELETE /uploads/{upload_id}?portal_url=...&input_key=... aborts the S3 multipart upload so it stops counting toward storage. Skipping this isn't catastrophic — incomplete multipart uploads are cleaned up automatically after a few days — but it's the clean way to cancel.

Polling for completion

GET /tasks/{task_id} works for a task from either option above. Poll until status is SUCCESS or FAILURE; result then holds either the published service info (PackageResult: service_url, item_id, ...) or a FailureResult.

Supported file types

.slpk .tpkx .las/.laz/.zlas .tif/.tiff/.img/.ecw .ifc/.rvt .osgb/.obj/.dae. An .ifc/.rvt may also be uploaded as a .zip bundle together with georeferencing sidecars (.prj/.wld3) — the model file inside determines the task.

POST /tasks/process_slpk / POST /tasks/process_tpkx and their matching GET endpoints are deprecated — use POST /publish and GET /tasks/{task_id} instead, which work for every supported file type.

Upload & publish

Proxy upload: send a file through this API to S3 and run it through extraction + publish, in one call or two. See the description above for the full sequence.

Submit Slpk Deprecated

header Parameters
x-client-id
required
string (X-Client-Id)
x-client-secret
required
string (X-Client-Secret)
x-portal-token
required
string (X-Portal-Token)
Request Body schema: application/json
required
portal_url
required
string (Portal Url)
input_key
required
string (Input Key)
service_name
string (Service Name)
Default: ""
referer
string (Referer)
Default: ""
register_source_item
boolean (Register Source Item)
Default: false
sharing_level
string (Sharing Level)
Default: "private"
group_ids
Array of strings (Group Ids)
Default: []
portal_refresh_token
string (Portal Refresh Token)
Default: ""

Responses

Request samples

Content type
application/json
{
  • "portal_url": "string",
  • "input_key": "string",
  • "service_name": "",
  • "referer": "",
  • "register_source_item": false,
  • "sharing_level": "private",
  • "group_ids": [ ],
  • "portal_refresh_token": ""
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "status": "string",
  • "poll_url": ""
}

Get Slpk Deprecated

path Parameters
task_id
required
string (Task Id)

Responses

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "status": "string",
  • "poll_url": "",
  • "info": { },
  • "result": {
    }
}

Submit Tpkx Deprecated

header Parameters
x-client-id
required
string (X-Client-Id)
x-client-secret
required
string (X-Client-Secret)
x-portal-token
required
string (X-Portal-Token)
Request Body schema: application/json
required
portal_url
required
string (Portal Url)
input_key
required
string (Input Key)
service_name
string (Service Name)
Default: ""
referer
string (Referer)
Default: ""
register_source_item
boolean (Register Source Item)
Default: false
sharing_level
string (Sharing Level)
Default: "private"
group_ids
Array of strings (Group Ids)
Default: []
portal_refresh_token
string (Portal Refresh Token)
Default: ""

Responses

Request samples

Content type
application/json
{
  • "portal_url": "string",
  • "input_key": "string",
  • "service_name": "",
  • "referer": "",
  • "register_source_item": false,
  • "sharing_level": "private",
  • "group_ids": [ ],
  • "portal_refresh_token": ""
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "status": "string",
  • "poll_url": ""
}

Get Tpkx Deprecated

path Parameters
task_id
required
string (Task Id)

Responses

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "status": "string",
  • "poll_url": "",
  • "info": { },
  • "result": {
    }
}

Get Task

path Parameters
task_id
required
string (Task Id)

Responses

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "status": "string",
  • "poll_url": "",
  • "info": { },
  • "result": {
    }
}

Upload Package

header Parameters
x-client-id
required
string (X-Client-Id)
x-client-secret
required
string (X-Client-Secret)
x-portal-token
string (X-Portal-Token)
Default:
Request Body schema: multipart/form-data
required
file
required
string <binary> (File)
portal_url
required
string (Portal Url)
auto_publish
boolean (Auto Publish)
Default: false
service_name
string (Service Name)
Default: ""
referer
string (Referer)
Default: ""
register_source_item
boolean (Register Source Item)
Default: false
sharing_level
string (Sharing Level)
Default: "private"
group_ids
string (Group Ids)
Default: ""

Responses

Response samples

Content type
application/json
null

Publish Package

header Parameters
x-client-id
required
string (X-Client-Id)
x-client-secret
required
string (X-Client-Secret)
x-portal-token
required
string (X-Portal-Token)
Request Body schema: application/json
required
portal_url
required
string (Portal Url)
input_key
required
string (Input Key)
service_name
string (Service Name)
Default: ""
referer
string (Referer)
Default: ""
register_source_item
boolean (Register Source Item)
Default: false
sharing_level
string (Sharing Level)
Default: "private"
group_ids
Array of strings (Group Ids)
Default: []
portal_refresh_token
string (Portal Refresh Token)
Default: ""

Responses

Request samples

Content type
application/json
{
  • "portal_url": "string",
  • "input_key": "string",
  • "service_name": "",
  • "referer": "",
  • "register_source_item": false,
  • "sharing_level": "private",
  • "group_ids": [ ],
  • "portal_refresh_token": ""
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "status": "string",
  • "poll_url": ""
}

Presigned upload (large files)

Upload straight to S3 via presigned URLs — this API only signs URLs, file bytes never pass through it. Recommended for large files. See the description above for the 4-step sequence.

Init Upload

header Parameters
x-client-id
required
string (X-Client-Id)
x-client-secret
required
string (X-Client-Secret)
Request Body schema: application/json
required
portal_url
required
string (Portal Url)
filename
required
string (Filename)
size
required
integer (Size)

Responses

Request samples

Content type
application/json
{
  • "portal_url": "string",
  • "filename": "string",
  • "size": 0
}

Response samples

Content type
application/json
{
  • "upload_id": "string",
  • "input_key": "string",
  • "part_size": 0
}

Sign Parts

header Parameters
x-client-id
required
string (X-Client-Id)
x-client-secret
required
string (X-Client-Secret)
Request Body schema: application/json
required
portal_url
required
string (Portal Url)
input_key
required
string (Input Key)
upload_id
required
string (Upload Id)
part_numbers
required
Array of integers (Part Numbers)

Responses

Request samples

Content type
application/json
{
  • "portal_url": "string",
  • "input_key": "string",
  • "upload_id": "string",
  • "part_numbers": [
    ]
}

Response samples

Content type
application/json
{
  • "urls": [
    ]
}

Complete Upload

header Parameters
x-client-id
required
string (X-Client-Id)
x-client-secret
required
string (X-Client-Secret)
x-portal-token
string (X-Portal-Token)
Default:
Request Body schema: application/json
required
portal_url
required
string (Portal Url)
input_key
required
string (Input Key)
upload_id
required
string (Upload Id)
required
Array of objects (Parts)
auto_publish
boolean (Auto Publish)
Default: false
service_name
string (Service Name)
Default: ""
referer
string (Referer)
Default: ""
register_source_item
boolean (Register Source Item)
Default: false
sharing_level
string (Sharing Level)
Default: "private"
group_ids
Array of strings (Group Ids)
Default: []
portal_refresh_token
string (Portal Refresh Token)
Default: ""

Responses

Request samples

Content type
application/json
{
  • "portal_url": "string",
  • "input_key": "string",
  • "upload_id": "string",
  • "parts": [
    ],
  • "auto_publish": false,
  • "service_name": "",
  • "referer": "",
  • "register_source_item": false,
  • "sharing_level": "private",
  • "group_ids": [ ],
  • "portal_refresh_token": ""
}

Response samples

Content type
application/json
{
  • "input_key": "string",
  • "task": {
    }
}

Abort Upload

path Parameters
upload_id
required
string (Upload Id)
query Parameters
portal_url
required
string (Portal Url)
input_key
required
string (Input Key)
header Parameters
x-client-id
required
string (X-Client-Id)
x-client-secret
required
string (X-Client-Secret)

Responses

Response samples

Content type
application/json
{
  • "detail": [
    ]
}

History & downloads

Look up past jobs and fetch a permanent link back to an originally-uploaded source file.

List Jobs

query Parameters
portal_url
required
string (Portal Url)
limit
integer (Limit)
Default: 10
offset
integer (Offset)
Default: 0
referer
string (Referer)
Default: ""
header Parameters
x-client-id
required
string (X-Client-Id)
x-client-secret
required
string (X-Client-Secret)
x-portal-token
required
string (X-Portal-Token)

Responses

Response samples

Content type
application/json
{
  • "persisted": true,
  • "jobs": [
    ]
}

Download Source File

query Parameters
portal_url
required
string (Portal Url)
input_key
required
string (Input Key)
header Parameters
x-client-id
required
string (X-Client-Id)
x-client-secret
required
string (X-Client-Secret)

Responses

Response samples

Content type
application/json
null

Operational

Liveness/readiness probes and a startup-config diagnostic view — not part of the integration flow.

Livez

Liveness: process is up. No dependency checks — cheap, never 503s on deps.

Responses

Response samples

Content type
application/json
{ }

Readyz

Readiness: can this pod actually serve? Broker reachable + clients.json parses.

Sync def so the blocking broker probe runs in FastAPI's threadpool, not the event loop. Deliberately does NOT check downstream portals, per-tenant S3, or worker health — those being down doesn't make this API unable to accept and queue work, and gating on them would take the API out on someone else's outage.

Responses

Response samples

Content type
application/json
{ }

Health

Responses

Response samples

Content type
application/json
{ }

Metrics

Endpoint that serves Prometheus metrics.

Responses

Response samples

Content type
application/json
null