SG Ground Truth

Endpoints

One card per call. What it takes, every status code it answers with, a real response, and the edge cases that live on the call rather than on a data type.

The path is the canonical spelling: <type> is the plural URL segment, <Type> the schema name. Every finding and recipe names the calls it covers in that same spelling, so each card carries the verdicts of everything that measured it.

Grouped by the resource each one acts on, in the order a client meets them. The grouping is read off the path, so a card cannot fall outside it.

A card marked partial or untested says on its own row what was not reached. Every one of them today is a webhook delivery call: on the site these probes run against, entity events reach no hook at all, so the delivery payload, X-SG-SIGNATURE and the batch headers cannot be recorded here. If you run a site where webhooks deliver, those are the entries to contribute. A probe and the response it actually got is the whole ask.

Session

  • GET / get_root

    The site's login configuration, answered without a token. Read user_authentication_method here before choosing a grant type.

    • auth
    • discovery
  • POST /auth/access_token post_auth_access_token

    Form-encode it. application/json is 415 naming the one legal type, and the 600s bearer is cheaper to re-mint than the refresh_token is to use.

    • auth
    • token
    • client
  • POST /internal_api/app_session_request post_internal_api_app_session_request

    Send appName and machineId, form or JSON, with no token and no cookie. The answer is a sessionRequestId to poll and a url a logged-in person opens in a browser to approve.

    • auth
    • token
    • user
    • launcher
  • PUT /internal_api/app_session_request/<sessionRequestId> put_internal_api_app_session_request_id

    Poll it with no body. {"approved": false} while pending; once, {"approved": true, "sessionToken", "userLogin"}; then 404 forever. Forgotten, denied and unknown all read the same 404.

    • auth
    • token
    • user
    • launcher

Site

  • GET /license_info get_license_info

    Seat counts in a {data, status} envelope, not the JSON:API one. rule decides whether free is a number or -1 for unlimited, and none of the three counts equals the HumanUser row count.

    • discovery
    • user
    • read-only
  • GET /preferences get_preferences

    The only place the unit behind a duration field is named. prefs narrows it to one key, and hours_per_day and duration_units are the pair a renderer needs.

    • schema
    • duration
    • discovery
  • PUT /preferences/update put_preferences_update

    Enables a custom entity slot and nothing else. On the probed site every body, valid or not, answered 400 code 111 Updating the preferences is not available, so the shape stays unverified.

    • custom-entity
    • write
    • error-handling
    • permission
  • GET /schedule/work_day_rules get_schedule_work_day_rules

    One row per calendar day, both ends inclusive, no paging at 730 rows. A project_id or user_id that does not exist answers 200 with the studio default instead of an error.

    • date
    • discovery
    • silent
    • read-only
  • PUT /schedule/work_day_rules put_schedule_work_day_rules

    One day per call, keyed by date in the body rather than by a path id, and user_id or project_id absent means the change applies to the studio default for everyone.

    • date
    • write
    • error-handling
    • project
    • user
  • GET /spec.<format> get_spec_format

    The site publishes its own OpenAPI v3 document, json or yaml, and it lists 62 operations where this corpus covers 23. The suffix is required and any other 406s.

    • schema
    • discovery
    • cost
  • GET /subscription_seat/user_subscriptions get_subscription_seat_user_subscriptions

    Returns a bare hash of user id to subscription string with no data and no links, holding only some HumanUser rows, and a null value means the user has no subscription rather than no such user.

    • discovery
    • user
    • read-only
  • POST /subscription_seat/user_subscriptions post_subscription_seat_user_subscriptions

    Body is a bare hash of user id to subscription string, not a JSON:API document. An unknown id is a whole-request 400, and a hash naming no user is a 200 returning {}.

    • discovery
    • user
    • write
    • error-handling

Schema

  • GET /schema get_schema

    The enabled type list, and the enablement test for a CustomEntityNN: a slot absent here 404s everywhere. 12KB, so fetch it once and never loop it into /fields.

    • schema
    • custom-entity
    • discovery
    • cost
  • GET /schema/<Type> get_schema_type

    One type's display name without its 48KB of fields, and the cheapest existence check there is: an unknown or unenabled type is 404 Entity type 'X' does not exist.

    • schema
    • custom-entity
    • discovery
  • GET /schema/<Type>/fields get_schema_type_fields

    Every field on one type with its data_type, editable and mandatory. The expensive call at 48KB and ~330ms, so fetch the types you need and never loop the /schema listing into it.

    • schema
    • cost
    • fill-rate
    • discovery
  • POST /schema/<Type>/fields post_schema_type_fields

    You send a display name and the server derives the sg_ name, which is only in links.self. A duplicate display name is 201 with a silent _1 suffix, so read /fields first.

    • schema
    • custom-field
    • create
    • silent
    • destructive
  • GET /schema/<Type>/fields/<field> get_schema_type_fields_field

    One field's properties, at 1211 bytes against 48KB for the whole type. Pass project_id or hidden_values is empty and your status picker offers statuses the project refuses.

    • schema
    • status
    • list-field
  • POST /schema/<Type>/fields/<field> post_schema_type_fields_field

    Revive a retired field, at 204. It is the only way to get a burnt name back, and it returns at its original data_type whatever the site wants now.

    • schema
    • custom-field
    • discovery
  • PUT /schema/<Type>/fields/<field> put_schema_type_fields_field

    Changes a field's properties. A body changing data_type is a 200 that does nothing, so read the field back rather than trusting the status code.

    • schema
    • custom-field
    • silent
  • DELETE /schema/<Type>/fields/<field> delete_schema_type_fields_field

    Retires a field at 204 and burns its programmatic name forever: the same name will not create again, only revive. Treat this as irreversible from REST.

    • schema
    • custom-field
    • destructive

Records

  • GET /entity/<type> get_entity_type

    Pages rows. An entity field is returned under relationships and never attributes, an unknown fields name is dropped at 200, and links.next is emitted on empty pages forever.

    • query
    • paging
    • filter
    • entity-field
    • silent
  • POST /entity/<type> post_entity_type

    project is the requirement on every project-scoped type and the identity field is not, whatever the schema says. ?fields is ignored, and the 201 returns the whole record.

    • write
    • create
    • entity-field
    • silent
  • GET /entity/<type>/<id> get_entity_type_id

    One row, and the only read where fields is honoured on a single record. A retired row is 404 here and 200 under options[return_only]=retired.

    • query
    • entity-field
  • POST /entity/<type>/<id> post_entity_type_id

    Revives a retired row. ?revive=1 is required and any JSON body is discarded, so this is not an update via POST.

    • write
    • trap
  • PUT /entity/<type>/<id> put_entity_type_id

    Updates and returns the whole record, 77 attribute keys for a Shot. A key left out of the body is unchanged rather than cleared, and an empty body is a 200 no-op.

    • write
    • silent
  • DELETE /entity/<type>/<id> delete_entity_type_id

    Retires a row at 204 with an empty body. It is not erased: the row reads 404 normally and 200 under options[return_only]=retired, and a second delete is 404.

    • write
    • destructive
  • GET /entity/<type>/<id>/<field> get_entity_type_id_field

    Reads one image or attachment field, and with ?alt redirects to the bytes. Every other data type is a 400, so this is not a cheap single-field read.

    • attachment
    • media
    • image
    • trap
  • GET /entity/<type>/<id>/relationships/<related_field> get_entity_type_id_relationships_field

    The entity or multi_entity link list on its own, unpaged and unsorted. page, fields and sort are accepted and ignored, and every link is returned in one body.

    • entity-field
    • multi-entity
    • cost
  • POST /entity/_batch post_entity_batch

    The key is requests, not data, and sending data is 400 requests is missing. It answers 200 rather than 201, and one bad request rolls the whole batch back.

    • write
    • batch
    • create
    • silent
  • PUT /entity/projects/<id>/_update_last_accessed put_entity_projects_id_update_last_accessed

    Stamps one user's last visit to a project. Write-only: a user_id that does not exist answers the same 200, and nothing readable over REST changes.

    • project
    • user
    • silent
  • POST /entity/<type>/_search post_entity_type_search

    The only way to send a filter the query string cannot express, and it refuses application/json at 415 naming both vendor types. api3_array cannot express or; api3_hash nests.

    • query
    • filter
    • operator
    • header
    • paging
    • silent
  • POST /entity/<type>/_summarize post_entity_type_summarize

    Counts without paging rows. One grouping returns a field's distinct values and their counts at ~300ms, so rank a shortlist with it and never scan every field.

    • query
    • fill-rate
    • cost
    • list-field
    • summary
  • POST /entity/_text_search post_entity_text_search

    Free-text search across several types at once, returning a flattened row that is not the _search shape. entity_types is required and its value doubles as the per-type filter.

    • query
    • filter
    • header
    • silent
  • POST /hierarchy/_expand post_hierarchy_expand

    Returns one level of the navigation tree the web interface draws. It refuses the vendor content types every other POST requires and accepts only application/json.

    • query
    • header
    • project
    • trap
  • POST /hierarchy/_search post_hierarchy_search

    Answers where a row sits in the navigation tree. search_criteria must be a hash keyed exactly entity, and every other shape is the same misleading size must be 1.

    • query
    • header
    • project
    • trap

Media

  • GET /entity/<type>/<id>/<field>/_upload get_entity_type_id_field_upload

    Step one of three. filename is a required query parameter and its absence is 400 filename is missing; the reply holds links.upload and a links.complete_upload already prefixed with /api/v1.

    • upload
    • media
    • image
    • async
  • POST /entity/<type>/<id>/<field>/_upload post_entity_type_id_field_upload

    The path behind links.complete_upload. upload_info is the init reply verbatim, plus an etags array when the init was multipart; upload_data is where display_name and tags are set.

    • multipart
    • etag
    • attachment
  • PUT /entity/<type>/<id>/<field>/_upload put_entity_type_id_field_upload

    The storage_service: "sg" upload target, on the Flow PT host. A site on s3 returns an S3 links.upload instead, and calling this route directly is 400 for four missing signature parameters.

    • storage
    • media
  • GET /entity/<type>/<id>/<field>/_upload/multipart get_entity_type_id_field_upload_multipart

    Step two of a multipart transfer, once per part after the first. Walk links.get_next_part rather than building it: each reply holds the part's presigned upload and the link to the part after.

    • multipart
    • etag
  • POST /entity/<type>/<id>/<field>/_upload/multipart_abort post_entity_type_id_field_upload_multipart_abort

    204 and an empty body. The body is the upload_info object flat at the top level, not the {"upload_info": ..., "upload_data": ...} wrapper the spec declares, which returns 400.

    • multipart
    • storage
  • GET /entity/<type>/<id>/_upload get_entity_type_id_upload

    The same handshake with the field left out of the path, which stores the bytes as an Attachment on attachment_links rather than on a field. The type must actually have that field.

    • upload
    • attachment
    • provenance
  • POST /entity/<type>/<id>/_upload post_entity_type_id_upload

    The fieldless completion, /entity/<type>/<id>/attachments/_upload. Same body contract as the field form, including etags for a multipart init; the row lands on attachment_links.

    • multipart
    • attachment
    • note
  • PUT /entity/<type>/<id>/_upload put_entity_type_id_upload

    The fieldless storage_service: "sg" upload target. The spec declares only filename and signature, and the site asks for user_id, user_type and expiration as well.

    • storage
    • attachment
  • GET /entity/<type>/<id>/_upload/multipart get_entity_type_id_upload_multipart

    The fieldless part chain, /entity/<type>/<id>/attachments/_upload/multipart. Same four checked parameters and the same unchecked upload_id as the field form.

    • multipart
    • attachment
  • POST /entity/<type>/<id>/_upload/multipart_abort post_entity_type_id_upload_multipart_abort

    The fieldless abort, /entity/<type>/<id>/attachments/_upload/multipart_abort. 204 on the flat upload_info object, identical to the field form in every respect but the path.

    • multipart
    • note
  • POST /transcode/attachment_metadata/<id> post_transcode_attachment_metadata_id

    Records video metadata for media transcoded outside Flow PT. 200 with a body of one space, and none of the six values reads back on the Attachment; only updated_at moves.

    • transcode
    • media
    • async
    • silent
  • POST <links.complete_upload> post_links_complete_upload

    Step three, at 201 with a body of a single space. Not JSON, and it never names the row it created, so parsing it crashes after the write has landed.

    • upload
    • attachment
    • async
    • silent
  • PUT <links.upload> put_links_upload

    Step two, to storage rather than to Flow PT, with no Authorization header. It is the only step that moves bytes, and skipping it still lets step three answer 201.

    • upload
    • media
    • attachment
    • silent

Attention

  • GET /entity/<type>/<id>/activity_stream get_entity_type_id_activity_stream

    The feed the web application draws, paged by max_id and min_id rather than by page[]. A record id that is not there answers 500, not the 404 the spec advertises.

    • follow
    • paging
    • user
    • async
    • trap
  • GET /entity/<type>/<id>/followers get_entity_type_id_followers

    The HumanUsers watching one record, whole and unpaged, with name the only attribute. links.self is spelled /entity/HumanUser/<id>, singular and CamelCase.

    • follow
    • user
    • paging
    • note
  • PUT /entity/<type>/<id>/unfollow put_entity_type_id_unfollow

    Removes one named user from one record at 204, and answers 204 again when that user was never following. It is PUT on the record, the mirror image of the POST on the user that follows.

    • follow
    • user
    • silent
    • error-handling
  • POST /entity/human_users/<user_id>/follow post_entity_human_users_id_follow

    Subscribes one HumanUser to a list of records at 204. entity must be the CamelCase schema name: the snake_case plural every path uses answers 500, and a bad id in the list 404s after applying the good ones.

    • follow
    • user
    • header
    • error-handling
    • silent
    • trap
  • GET /entity/human_users/<user_id>/following get_entity_human_users_id_following

    Everything one HumanUser follows, unpaged in a single body, filterable only by entity and project_id. An ApiUser id is a 404, so a script has no follow list of its own.

    • follow
    • user
    • paging
    • project
    • cost
  • GET /entity/notes/<id>/thread_contents get_entity_notes_id_thread_contents

    A Note, its Attachments and its Replies as one flat list in time order. The Note and the Attachments name their author under created_by, a Reply names it under user.

    • note
    • reply
    • attachment
    • user

Webhooks

  • GET /webhook/deliveries/<record_uuid> get_webhook_deliveries_record_uuid partial

    Returns one delivery record with ten keys. status is delivered even when nothing answered, so read response_code, which is 0 when no response was received.

    Not measured. Measured against a Webhook_Status_Change delivery to a dead host. request_headers, response_headers, body and a non-zero response_code are unmeasured.

    • webhook
    • delivery
    • error-handling
  • PUT /webhook/deliveries/<record_uuid> put_webhook_deliveries_record_uuid partial

    Answers 200 for an empty body, for a key it does not take, and for a valid acknowledgement that then reads back null. Only the 4096-byte cap is enforced.

    Not measured. The acknowledgement never persisted on the probed site, where the webhook subsystem is degraded. Whether that is the API or the site is unresolved.

    • webhook
    • delivery
    • silent
    • trap
  • POST /webhook/deliveries/<record_uuid>/redeliver post_webhook_deliveries_record_uuid_redeliver partial

    Answers 204 with no body. On the probed site no second delivery record followed, so 204 reports that the request was accepted and nothing more.

    Not measured. Answers 204 and produced no second delivery on the probed site. Whether it redelivers anywhere is unmeasured.

    • webhook
    • delivery
    • silent
  • GET /webhook/hooks get_webhook_hooks

    Lists every hook on the site, not only this script's. status takes active or disabled and a value no hook has answers 200 with zero rows rather than 400.

    • webhook
    • paging
    • silent
  • POST /webhook/hooks post_webhook_hooks

    url and entity_types are required and the entity type and action are checked. A field name, a project id and a second entity type are all accepted without being checked.

    • webhook
    • create
    • silent
    • trap
    • token
  • GET /webhook/hooks/<hook_id>/deliveries get_webhook_hooks_hook_id_deliveries partial

    Takes status, entity_type, entity_id, from and acknowledgement as query params and answers 200 with zero rows for any of them. No delivery was observed, so the record shape is unprobed.

    Not measured. Only Webhook_Status_Change deliveries were observed. The record for an entity event, and every field that only an answering consumer fills, are unmeasured.

    • webhook
    • delivery
    • paging
    • filter
  • GET /webhook/hooks/<record_uuid> get_webhook_hooks_record_uuid

    Returns the hook without its token. A well-formed uuid naming nothing answers 404 code 104, a segment that is not a uuid answers 404 code 103 with detail null.

    • webhook
    • error-handling
  • PUT /webhook/hooks/<record_uuid> put_webhook_hooks_record_uuid

    A partial body edits only the keys it names. An empty body is 400, and status takes active or disabled and names both in the error.

    • webhook
    • write
    • status
  • DELETE /webhook/hooks/<record_uuid> delete_webhook_hooks_record_uuid

    204 and the hook is gone at once: the hook, its deliveries listing and a second delete all answer 404 immediately after.

    • webhook
    • destructive
  • POST /webhook/hooks/<record_uuid>/test_connection post_webhook_hooks_record_uuid_test_connection partial

    Answers 204 for any uuid, a hook that does not exist included, and confirms nothing about the hook, the endpoint or whether anything was sent.

    Not measured. Answers 204 for any uuid and produced no delivery record on the probed site. What it does on a working site is unmeasured.

    • webhook
    • silent
    • trap

Exports

  • GET /exports/page/<page_id>.<format> get_exports_page_id_format

    Exports a page's default view. Off unless a site admin marked the view exportable: on the probed site all 52 pages sampled answered 422, and no field says which pages will work.

    • page
    • error-handling
  • GET /exports/page/<page_id>/<layout_name>.<format> get_exports_page_id_layout_format

    The same export addressed at one named view, needed when a page has several. A layout name that does not exist is indistinguishable from one that does, because both answer the page-level 422.

    • page

Other

  • POST /internal_api/autodesk_identity/license_renewal post_internal_api_autodesk_identity_license_renewal

    Renews the Autodesk Identity licence lease behind the _session_id cookie: {"message": "OK"} and license.expiresAt moves to now plus one day. 401 without the cookie.

    • auth
    • user
    • launcher
  • GET /internal_api/session get_internal_api_session

    With the session token as the _session_id cookie, answers when the session and the licence lease expire, in epoch seconds. Reading it does not renew anything. 401 without the cookie.

    • auth
    • token
    • user
    • launcher
  • POST /internal_api/session post_internal_api_session

    Renews the session behind the _session_id cookie: {"message": "OK"} and expiresAt moves to now plus the site's expiry window. 401 without the cookie.

    • auth
    • token
    • user
    • launcher

Every entry on this site is the output of a probe in probes/. The corpus is generated by running those probes against a live Flow Production Tracking site, not written from memory.

Not affiliated with or endorsed by Autodesk. Flow Production Tracking is their product; this is an independent record of how its REST API answers.