{"activeVersionTag":"latest","latestAvailableVersionTag":"latest","collection":{"info":{"_postman_id":"080248a6-e94a-46e0-bbb6-5dbd8d0ec4c6","name":"Knackly API (v1)","description":"## Key Terms in the Knackly API\n\n- **Workspace** - a given customer's site on Knackly.\n    \n- [<b>Catalogs</b>](#873f9e6e-0829-4ef2-a585-eef2e1f1bd4f) - a database / repository / table where the customer's data is stored and related documents are generated. One workspace can contain any number of catalogs. A catalog is the boundary for data access permissions in Knackly: groups of users have rights to view/modify data, or see the documents produced from that data, based on whether they have access to the catalog or not. API keys are also granted access on a per-Catalog basis.\n    \n- [<b>Schema</b>](#d106dd99-062a-4c48-a444-99d0bb4ff8f8) - Knackly's term for a schema (also called a Model). Each catalog has (or is based on) a model. Different catalogs may be based on different models, or multiple catalogs may be based on the same model. A model defines Variables, which in turn define the \"shape\" of an instance of that model. Models are object-based. Variables can be scalars or arrays; data types include \"text\", \"number\", \"date\", \"true/false\", and \"object\". (Object-type variables also define which model the object data must adhere to.) There are also \"selection\" variables, where the answer acts as a foreign key (usually a text string) referring by key to some other object (which itself is stored in either a Table, an external Query, or elsewhere within the record itself).\n    \n- [<b>Records</b>](#74f947c7-2d45-47be-893a-4391dec2d33e) - a single item in a catalog -- a data object that adheres to the model of the catalog in which it is stored. In other words, the records in a catalog are _instances_ of the catalog's model.\n    \n- **Template** - Knackly generates documents based on Templates. Templates are created and maintained by workspace administrators in Knackly. 1 template x 1 record (or other object) = 1 document.\n    \n- [<b>Apps</b>](#7e4867f1-f236-4099-93f3-8fa05b9b9033) (Knackly App) - a process within Knackly, defined by a workspace administrator, that interactively gathers data from an end-user, stores it in a record (in a catalog), and generates some number of documents from that data. There is no limit to how many documents (based on templates) that a given app can produce; it can be fixed in the app's definition or dynamic based on the data provided. Multiple different apps can also be run against the same record at different times. Note: This is different from your application (the software you're building that integrates with Knackly).\n    \n\n## API v1 Terminology Note\n\n**Important:** The v1 API uses `/items/` in URL paths, but throughout this documentation we refer to these as \"records\" (the official Knackly terminology). For example:\n\n- API path: `GET /:workspace/api/v1/catalogs/:catalog/items/:record_id`\n    \n- Documentation location: \"Catalogs > Records > Get record\"\n    \n\nThis terminology mismatch is a legacy issue from before terminology was standardized. It will be corrected in API v2.\n\n## Quick Start\n\n1. **Get API Credentials:** Obtain your API Key ID and Secret from your Knackly workspace administrator (see [API Keys and Permissions](#api-keys-and-permissions) for more information)\n    \n2. **Authenticate:** Call [Request an Access Token](#74af1701-f9f8-4c87-81f2-cafdb96160ef) (`POST /:workspace/api/v1/auth/login`) with your credentials to get an access token\n    \n3. **List Catalogs:** Call [List catalogs](#dbb8772a-5e30-454d-8b6e-65433efc553a) (`GET /:workspace/api/v1/catalogs`) to see available catalogs\n    \n4. **Get App Schema:** Call [Get app model(s)](#821e771c-0b1f-434f-9055-ff6a28b48b40) (`GET /:workspace/api/v1/catalogs/:catalog/apps/:app_name/types`) to understand the data structure (see also [Data Structure: Nested vs Flat](#data-structure-nested-vs-flat))\n    \n5. **Create Record:** Call [Create record by running app](#e063d16f-9204-476a-be7d-c70de2cfd813) (`POST /:workspace/api/v1/catalogs/:catalog/apps/:app_name`) with your data\n    \n6. **Check Status:** Poll [Get app status](#773b8d31-af3a-42f7-9262-bfbcb1a6c3fe) (`GET /:workspace/api/v1/catalogs/:catalog/items/:record_id/apps/:app_name`) until status is \"Completed\" (note: path says \"items\" but this is a \"record\"; see [Status Values](#status-values) for all status values)\n    \n7. **Download Documents:** Use the `files[].url` from the status response to download generated documents\n    \n\n## API Keys and Permissions\n\nIn Knackly, API keys exist _alongside_ Users as entities that can be assigned permissions. There are two types of API keys:\n\n### Regular API Keys\n\nRegular API keys allow operations analogous to what a regular/internal user can do in Knackly:\n\n- View and modify records in catalogs they have access to\n    \n- Run apps on records\n    \n- Generate documents\n    \n- Access catalog/app metadata\n    \n\nRegular API keys are granted access on a per-catalog basis via the `permissions` array (list of catalog IDs).\n\n### Admin API Keys\n\nAdmin API keys allow operations analogous to what an admin user can do:\n\n- All operations available to regular API keys, PLUS:\n    \n- Create, modify, and delete elements (Models, Tables, Queries)\n    \n- Manage template upload jobs (update DOCX or PDF binaries for templates in models)\n    \n- Create, modify, and delete users (admin, regular, or external)\n    \n- Create, modify, and delete groups\n    \n- Create, modify, and delete other API keys (both regular and admin)\n    \n\nAdmin API keys are created with `hasAdminAccess: true` when using the [Create API key](#2678618d-68f7-43eb-87d9-eb354d481a64) endpoint (requires an admin API key to call).\n\n**Which API key type do you need?**\n\n- Use a **regular API key** if you're building an integration that reads/writes records and generates documents\n    \n- Use an **admin API key** if you need to manage the workspace structure (catalogs, models, users, etc.) or if you're building administrative tooling\n    \n\n**In this documentation:**  \nOperations that require an admin API key are marked with \"(admin)\" in their title.\n\n## Data Structure: Nested vs Flat\n\nKnackly supports two ways to structure data when creating or updating records. The `flat` query parameter (default: `false`) should align with the format you're using.\n\n#### Nested Structure (default, `flat=false` or omitted):\n\nThis matches standard JSON with nested objects and arrays, corresponding to the object-based view you get from the \"Get app model(s)\" endpoint:\n\n``` json\n{\n  \"Client\": {\n    \"Name\": \"John Doe\",\n    \"Address\": {\n      \"Street\": \"123 Main St\",\n      \"City\": \"Anytown\"\n    }\n  },\n  \"Children\": [\n    { \"Name\": \"Child 1\", \"Age\": 10 },\n    { \"Name\": \"Child 2\", \"Age\": 8 }\n  ]\n}\n\n ```\n\n#### Flat Structure (`flat=true`):\n\nThis uses a flat object where property names are dot-separated paths to the data points, corresponding to the flat list you get from the \"Get app data points\" (fields) endpoint:\n\n``` json\n{\n  \"Client.Name\": \"John Doe\",\n  \"Client.Address.Street\": \"123 Main St\",\n  \"Client.Address.City\": \"Anytown\",\n  \"Children[0].Name\": \"Child 1\",\n  \"Children[0].Age\": 10,\n  \"Children[1].Name\": \"Child 2\",\n  \"Children[1].Age\": 8\n}\n\n ```\n\n#### Which format should you use?\n\n- Use **nested** (the default) if you're working with the model/schema information from [Get app model(s)](#821e771c-0b1f-434f-9055-ff6a28b48b40) (`GET /catalogs/:catalog/apps/:app_name/types`) - the structure naturally matches\n    \n- Use **flat** if you're working with the fields/data points from [Get app data points](#5a06e5a3-df76-42bf-bf86-3987df7a2b02) (`GET /catalogs/:catalog/apps/:app_name/fields`) - the dot-separated property names match the field paths\n    \n- Consider using **flat** if your application natively stores data in a flattened format (e.g., form submissions, CSV imports, key-value stores)\n    \n\n#### Endpoints that support flat parameter:\n\n##### For POST/PUT requests (where you're sending data):\n\n- `POST /catalogs/:catalog/apps/:app_name` (Create record by running app) - instructs Knackly how to interpret request body\n    \n- `POST /catalogs/:catalog/items` (Create record) - instructs Knackly how to interpret request body\n    \n- `PUT /catalogs/:catalog/items/:record_id` (Modify record) - instructs Knackly how to interpret request body\n    \n\n##### For GET requests (where you're receiving data):\n\n- `GET /catalogs/:catalog/items/:record_id (Get record)` - controls response format\n    \n- `GET /catalogs/:catalog/items (List records)` - controls response format\n    \n\n## Launching User App Sessions\n\nYou can send users from your application into Knackly by redirecting their browser to specific URLs. Use this when you want them to:\n\n- Run an app to review, change, or add data in an existing record, or to create a new record in a catalog; or\n    \n- Open the record details page to view metadata and documents for an existing record that has had one or more apps run on it.\n    \n\nKnackly exposes the following URL patterns and optional query parameters to support these flows.\n\n### Open an app in the browser\n\n- New record (create a record in a catalog with a given app):\n    \n\n`https://go.knackly.io/{workspace}/{catalog}/{app_name}`\n\n- Existing record (run an app on an existing record):\n    \n\n`https://go.knackly.io/{workspace}/{catalog}/{record_id}/{app_name}`\n\nReplace {workspace}, {catalog}, {app_name}, and {record_id} with your workspace name, catalog name, app name, and record ID. You can get app URLs for a catalog from List catalogs or List apps; the “run app on existing record” URL is also returned in the apps\\[\\].url field when you get a record or app status.\n\n#### App page – optional query parameters\n\n| Parameter | Description |\n| --- | --- |\n| `return_complete` | (Optional) URL to send the user to when they complete the app. Must be URL-encoded. |\n| `return_incomplete` | (Optional) URL to send the user to when they leave without completing (e.g. “Finish Later”). Must be URL-encoded. |\n| `disable_finish_later` (or `dfl` alias) | (Optional) Hides the “Finish Later” button. Example: ?disable_finish_later=true or ?dfl=true. |\n| `external` | (Optional) Valid external token for an external user; see External Tokens. |\n\nExample (with return URL and suppressing “Finish Later”):\n\n`https://go.knackly.io/{workspace}/{catalog}/{app_name}?return_complete={url}&dfl=1`\n\n### Record details page (view metadata and documents)\n\nTo let users view metadata and generated documents for an existing record (without running an app), redirect them to the record details page: `{base_url}/{workspace}/records/{catalog}/{record_id}` (use your workspace base URL, e.g. `https://go.knackly.io`).\n\nExample: `https://go.knackly.io/acme/records/Contracts/507f1f77bcf86cd799439011`\n\n#### Record details page – optional query parameters\n\nUpdate: New URL parameters are available to control UI behavior and a custom action button that triggers a webhook.\n\n| Parameter | Description |\n| --- | --- |\n| `disable_download` (or `dd`) | (Optional) Hides the download button and disables download links for individual documents. |\n| `disable_download_all` (or `dda`) | (Optional) Hides the \"download all\" button that normally lets users download a zip file including multiple documents. |\n| `custom_button_label` (or `cbl`) | (Optional) Label for a custom button. |\n| `custom_button_action` (or `cba`) | (Optional) Webhook ID to invoke when the button is clicked. |\n\nWhen the custom button is clicked, Knackly sends a webhook event of type `catalog.app.custom_action` to the webhook specified by custom_button_action (e.g. cba=wh_123456).\n\nExample (hide download, add “Send to CRM” button):\n\n`?disable_download_all=true&custom_button_label=Send+to+CRM&custom_button_action=wh_123456`\n\nThis shows a button labeled “Send to CRM” that triggers webhook `wh_123456`.\n\n## Status Values\n\nKnackly uses status values to indicate the current state of Records and Apps. Each Record has a status, and each App run on that record also has its own status.\n\n**Important:** Status values are **read-only** - you never need to provide status values in API requests. They are returned by the API in responses, and you need to understand them to correctly interpret the state of records and apps.\n\n#### Record Status Values\n\nRecord status indicates whether the record is available for operations:\n\n- **`Available`** - The record is available for operations (read, modify, run apps, etc.)\n    \n    - **Legacy mapping:** Previously \"Ok\" or \"Needs Updating\"\n        \n- **`In Use`** - The record is currently locked because a user is actively working with it. You should NOT attempt to modify the record while it's in this state.\n    \n    - **Legacy mapping:** Previously \"In Progress\"\n        \n\n##### **When checking record status:**\n\n- Use `Available` to determine if it's safe to perform operations on the record\n    \n- If status is `In Use`, wait before attempting modifications (the user may be in an interactive app session)\n    \n\n#### App Status Values\n\nApp status indicates the state of a specific app run on a record. Each app within a record can have its own status:\n\n- **`In Use`** - A user is currently interactively filling out the interview for this app. The record is locked.\n    \n    - **Legacy mapping:** Previously \"Needs Updating\" (but you couldn't tell if a user was actively in the app)\n        \n- **`Running`** - The user clicked Complete and documents are currently being generated.\n    \n    - **Legacy mapping:** Previously \"In Progress\"\n        \n- **`Completed`** - Documents were generated successfully and the app has not been run since then.\n    \n    - **Legacy mapping:** Previously \"Ok\"\n        \n- **`Error`** - One or more documents FAILED to generate, and the app has not been run since then.\n    \n    - **New status** - No legacy equivalent\n        \n- **`Modified`** - The app was run and data was modified, but the user did NOT click Complete (either clicked \"Finish Later\" or the interview session timed out).\n    \n    - **Legacy mapping:** Previously \"Needs Updating\" OR \"Incomplete\" (but you couldn't tell if a user was still in the app)\n        \n\n##### **When checking app status:**\n\n- Use `Completed` to know documents are ready to download\n    \n- Use `Error` to know document generation failed and may need attention\n    \n- Use `Modified` to know data was changed but documents weren't generated yet\n    \n- Use `In Use` or `Running` to know you should wait before running the app again\n    \n\n## Rate Limiting and API Usage\n\nTo ensure fair usage and avoid potential issues:\n\n- Avoid rapid polling or unnecessarily frequent API calls\n    \n- Implement reasonable delays between requests when checking status or polling for completion\n    \n- If rate limits are implemented in the future, you may receive a `429 Too Many Requests` response with headers indicating when you can retry\n    \n\n##### **API Use Cases and Pricing:**\n\n**Interactive:** Provide data as a starting point for users who want to interactively review/modify that data, and potentially enter additional data, prior to documents being generated.\n\n**Headless:** Generate documents based purely on data you provide, bypassing the standard Knackly interactive app experience. This use case may be subject to different pricing models.\n\nTalk to Sales about API pricing and confirm whether your intended use case is supported.","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","isPublicCollection":false,"owner":"6868588","collectionId":"080248a6-e94a-46e0-bbb6-5dbd8d0ec4c6","publishedId":"SzS7QReU","public":true,"publicUrl":"https://documenter-api.postman.tech/view/6868588/SzS7QReU","privateUrl":"https://go.postman.co/documentation/6868588-080248a6-e94a-46e0-bbb6-5dbd8d0ec4c6","customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"EF5B25"},"documentationLayout":"classic-double-column","version":"8.10.0","publishDate":"2020-03-20T13:17:24.000Z","activeVersionTag":"latest","documentationTheme":"light","metaTags":{},"logos":{}},"statusCode":200},"environments":[],"user":{"authenticated":false,"permissions":{"publish":false}},"run":{"button":{"js":"https://run.pstmn.io/button.js","css":"https://run.pstmn.io/button.css"}},"web":"https://www.getpostman.com/","team":{"logo":"https://res.cloudinary.com/postman/image/upload/t_team_logo_pubdoc/v1/team/768118b36f06c94b0306958b980558e6915839447e859fe16906e29d683976f0","favicon":""},"isEnvFetchError":false,"languages":"[{\"key\":\"csharp\",\"label\":\"C#\",\"variant\":\"HttpClient\"},{\"key\":\"csharp\",\"label\":\"C#\",\"variant\":\"RestSharp\"},{\"key\":\"curl\",\"label\":\"cURL\",\"variant\":\"cURL\"},{\"key\":\"dart\",\"label\":\"Dart\",\"variant\":\"http\"},{\"key\":\"go\",\"label\":\"Go\",\"variant\":\"Native\"},{\"key\":\"http\",\"label\":\"HTTP\",\"variant\":\"HTTP\"},{\"key\":\"java\",\"label\":\"Java\",\"variant\":\"OkHttp\"},{\"key\":\"java\",\"label\":\"Java\",\"variant\":\"Unirest\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"Fetch\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"jQuery\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"XHR\"},{\"key\":\"c\",\"label\":\"C\",\"variant\":\"libcurl\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Axios\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Native\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Request\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Unirest\"},{\"key\":\"objective-c\",\"label\":\"Objective-C\",\"variant\":\"NSURLSession\"},{\"key\":\"ocaml\",\"label\":\"OCaml\",\"variant\":\"Cohttp\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"cURL\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"Guzzle\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"HTTP_Request2\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"pecl_http\"},{\"key\":\"powershell\",\"label\":\"PowerShell\",\"variant\":\"RestMethod\"},{\"key\":\"python\",\"label\":\"Python\",\"variant\":\"http.client\"},{\"key\":\"python\",\"label\":\"Python\",\"variant\":\"Requests\"},{\"key\":\"r\",\"label\":\"R\",\"variant\":\"httr\"},{\"key\":\"r\",\"label\":\"R\",\"variant\":\"RCurl\"},{\"key\":\"ruby\",\"label\":\"Ruby\",\"variant\":\"Net::HTTP\"},{\"key\":\"shell\",\"label\":\"Shell\",\"variant\":\"Httpie\"},{\"key\":\"shell\",\"label\":\"Shell\",\"variant\":\"wget\"},{\"key\":\"swift\",\"label\":\"Swift\",\"variant\":\"URLSession\"}]","languageSettings":[{"key":"csharp","label":"C#","variant":"HttpClient"},{"key":"csharp","label":"C#","variant":"RestSharp"},{"key":"curl","label":"cURL","variant":"cURL"},{"key":"dart","label":"Dart","variant":"http"},{"key":"go","label":"Go","variant":"Native"},{"key":"http","label":"HTTP","variant":"HTTP"},{"key":"java","label":"Java","variant":"OkHttp"},{"key":"java","label":"Java","variant":"Unirest"},{"key":"javascript","label":"JavaScript","variant":"Fetch"},{"key":"javascript","label":"JavaScript","variant":"jQuery"},{"key":"javascript","label":"JavaScript","variant":"XHR"},{"key":"c","label":"C","variant":"libcurl"},{"key":"nodejs","label":"NodeJs","variant":"Axios"},{"key":"nodejs","label":"NodeJs","variant":"Native"},{"key":"nodejs","label":"NodeJs","variant":"Request"},{"key":"nodejs","label":"NodeJs","variant":"Unirest"},{"key":"objective-c","label":"Objective-C","variant":"NSURLSession"},{"key":"ocaml","label":"OCaml","variant":"Cohttp"},{"key":"php","label":"PHP","variant":"cURL"},{"key":"php","label":"PHP","variant":"Guzzle"},{"key":"php","label":"PHP","variant":"HTTP_Request2"},{"key":"php","label":"PHP","variant":"pecl_http"},{"key":"powershell","label":"PowerShell","variant":"RestMethod"},{"key":"python","label":"Python","variant":"http.client"},{"key":"python","label":"Python","variant":"Requests"},{"key":"r","label":"R","variant":"httr"},{"key":"r","label":"R","variant":"RCurl"},{"key":"ruby","label":"Ruby","variant":"Net::HTTP"},{"key":"shell","label":"Shell","variant":"Httpie"},{"key":"shell","label":"Shell","variant":"wget"},{"key":"swift","label":"Swift","variant":"URLSession"}],"languageOptions":[{"label":"C# - HttpClient","value":"csharp - HttpClient - C#"},{"label":"C# - RestSharp","value":"csharp - RestSharp - C#"},{"label":"cURL - cURL","value":"curl - cURL - cURL"},{"label":"Dart - http","value":"dart - http - Dart"},{"label":"Go - Native","value":"go - Native - Go"},{"label":"HTTP - HTTP","value":"http - HTTP - HTTP"},{"label":"Java - OkHttp","value":"java - OkHttp - Java"},{"label":"Java - Unirest","value":"java - Unirest - Java"},{"label":"JavaScript - Fetch","value":"javascript - Fetch - JavaScript"},{"label":"JavaScript - jQuery","value":"javascript - jQuery - JavaScript"},{"label":"JavaScript - XHR","value":"javascript - XHR - JavaScript"},{"label":"C - libcurl","value":"c - libcurl - C"},{"label":"NodeJs - Axios","value":"nodejs - Axios - NodeJs"},{"label":"NodeJs - Native","value":"nodejs - Native - NodeJs"},{"label":"NodeJs - Request","value":"nodejs - Request - NodeJs"},{"label":"NodeJs - Unirest","value":"nodejs - Unirest - NodeJs"},{"label":"Objective-C - NSURLSession","value":"objective-c - NSURLSession - Objective-C"},{"label":"OCaml - Cohttp","value":"ocaml - Cohttp - OCaml"},{"label":"PHP - cURL","value":"php - cURL - PHP"},{"label":"PHP - Guzzle","value":"php - Guzzle - PHP"},{"label":"PHP - HTTP_Request2","value":"php - HTTP_Request2 - PHP"},{"label":"PHP - pecl_http","value":"php - pecl_http - PHP"},{"label":"PowerShell - RestMethod","value":"powershell - RestMethod - PowerShell"},{"label":"Python - http.client","value":"python - http.client - Python"},{"label":"Python - Requests","value":"python - Requests - Python"},{"label":"R - httr","value":"r - httr - R"},{"label":"R - RCurl","value":"r - RCurl - R"},{"label":"Ruby - Net::HTTP","value":"ruby - Net::HTTP - Ruby"},{"label":"Shell - Httpie","value":"shell - Httpie - Shell"},{"label":"Shell - wget","value":"shell - wget - Shell"},{"label":"Swift - URLSession","value":"swift - URLSession - Swift"}],"layoutOptions":[{"value":"classic-single-column","label":"Single Column"},{"value":"classic-double-column","label":"Double Column"}],"versionOptions":[],"environmentOptions":[{"value":"0","label":"No Environment"}],"canonicalUrl":"https://documenter.gw.postman.com/view/metadata/SzS7QReU"}