# Authentication Source: https://developer.folk.app/api-reference/authentication folk's API uses API keys to authenticate requests. You can view and manage your API keys in your [workspace settings](https://app.folk.app/apps/contacts/network/settings/api-keys) under the "API" section. To authenticate a request, you need to include the API key in the `Authorization` header using the bearer token scheme. ``` Authorization: Bearer ``` API requests without authentication will fail with a `401 Unauthorized` status code. # Batch requests Source: https://developer.folk.app/api-reference/batch-requests Batch endpoints let you act on up to **100 records** in a single request. Each record is handled on its own: one record failing, for instance because it does not exist, does not prevent the others from going through. ## Sending a batch A batch endpoint is a `POST` to a `/batch/{operation}` path, such as `/v1/people/batch/delete`. The body holds a `records` array. ```bash theme={null} curl -X POST "https://api.folk.app/v1/people/batch/delete" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "records": [ { "id": "per_183ed5cc-3182-45de-84d1-d520f2604810" }, { "id": "per_7a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d" } ] }' ``` A request with no records is accepted and does nothing. ## Reading the response Once the request itself is valid, the response is `200`, whatever happened to each record. The body holds one entry in `results` per record you sent, in the order you sent them: the first result is the outcome of the first record, and so on. Each result carries a `status`: * `succeeded`: the operation was applied. `data` holds the same result as the single-record endpoint, such as the ID of the deleted record. When part of the operation could not be applied, such as adding a created person to one of its groups, `warnings` lists what was left out. Some endpoints add their own fields beside `data`, such as `removedGroups` for [update people in batch](/api-reference/people/update-people-in-batch) and [update companies in batch](/api-reference/companies/update-companies-in-batch) and `operation` for [create companies in batch](/api-reference/companies/create-companies-in-batch). * `failed`: the operation was not applied to this record. `error` explains why, with the same `code`, `message` and `documentationUrl` as the [errors](/api-reference/errors) of single-record endpoints. ```json theme={null} { "data": { "results": [ { "status": "succeeded", "data": { "id": "per_183ed5cc-3182-45de-84d1-d520f2604810" } }, { "status": "failed", "error": { "code": "NOT_FOUND", "message": "Person not found.", "documentationUrl": "https://developer.folk.app/api-reference/errors#not-found" } } ] } } ``` Records are processed together rather than one after the other in the order you sent them: a record cannot rely on the outcome of another record in the same request, unless the endpoint says otherwise. Check the `status` of every result rather than the status code: a `200` does not mean every record succeeded. ## Requests rejected as a whole The whole request is rejected, and no record is processed, when: * the request is not authenticated (`401`), or exceeds the [rate limit](/api-reference/rate-limits) (`429`); * the body is not valid JSON (`400`); * a record does not match the expected format (such as a malformed ID or a field of the wrong type), or the body has more than 100 records (`422`). `error.details.issues` gives the position of each invalid record, such as `["records", 3, "id"]`. A record that has the expected format but cannot be applied, such as one that names a group you cannot access, fails on its own and does not stop the others. These keep the status codes described in [errors](/api-reference/errors), and `error.details.issues` says what is wrong. For instance, a request with more than 100 records returns: ```json theme={null} { "error": { "code": "UNPROCESSABLE_ENTITY", "message": "Invalid input", "documentationUrl": "https://developer.folk.app/api-reference/errors#unprocessable-entity", "details": { "issues": [ { "code": "too_big", "maximum": 100, "type": "array", "inclusive": true, "exact": false, "message": "A batch accepts at most 100 records.", "path": ["records"] } ] }, "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z" } } ``` Split larger sets into several requests of at most 100 records. ## Retrying a batch Batch endpoints accept an [`Idempotency-Key`](/api-reference/idempotency). A retry with the same key and body returns the original response, including the result of every record, without running any of them again. To retry only the records that failed, send them in a new request with a new key. # Create a company Source: https://developer.folk.app/api-reference/companies/create-a-company post /v1/companies Create a new company in the workspace. Create a company in the workspace, optionally adding them to one or more groups. In the app, companies without groups will only be visible through the "Search" section. Company names are unique across the workspace. If you try to create a company with a name that already exists, the existing company will be returned. # Create companies in batch Source: https://developer.folk.app/api-reference/companies/create-companies-in-batch post /v1/companies/batch/create Create up to 100 companies in the workspace. A record naming an existing company updates that company, as `POST /v1/companies` does, and its result says so. Each record is processed on its own: one that fails does not prevent the others from being created. Create up to 100 companies in the workspace, each optionally added to one or more groups. Each record takes the same fields as [create a company](/api-reference/companies/create-a-company) and is processed on its own: a record that fails, for instance because it names a group you cannot access, does not prevent the others from being created. See [batch requests](/api-reference/batch-requests) for the 100-record limit and how to read the response. Each company comes back in full, as in the response of [create a company](/api-reference/companies/create-a-company), and `operation` says what the record did: * `created`: no company had this name, so a new one was created. * `updated`: a company already had this name, so that company was updated instead, as [create a company](/api-reference/companies/create-a-company) does. Its fields are replaced by the ones the record provides, and the record's groups, emails, phones, URLs, addresses and multiple-choice values are added to the existing ones. Records of one request sharing a name end up as one company, as if they had been sent one after the other to [create a company](/api-reference/companies/create-a-company): the first record creates the company and the next ones update it. All of them return the same company. Names match exactly, case included: `Acme`, `acme` and `ACME` are three different companies. When importing a list whose names are not consistently cased, normalize them first, or each casing creates its own company. A record without a `name` always creates a new company, without a name. A record that succeeded but could not be fully set up, for instance because the company could not be added to one of its groups, carries a `warnings` list explaining what was not applied. # Delete a company Source: https://developer.folk.app/api-reference/companies/delete-a-company delete /v1/companies/{companyId} Delete an existing company in the workspace. This action is not reversible. When removing a company from the workspace, all of its information is deleted, including its custom field values, notes, and any associations with groups, people, and other entities. # Delete companies in batch Source: https://developer.folk.app/api-reference/companies/delete-companies-in-batch post /v1/companies/batch/delete Delete up to 100 existing companies in the workspace. Each record is deleted on its own: one that fails does not prevent the others from being deleted. This action is not reversible. When removing a company from the workspace, all of its information is deleted, including its custom field values, notes, and any associations with groups, people, and other entities. Each company is deleted on its own, and the response gives the outcome of every record. See [batch requests](/api-reference/batch-requests) for the 100-record limit and how to read the response. A company designated by several records, by the same ID or through the ID of a company merged into it, is deleted once, and each of those records reports it as deleted. # Get a company Source: https://developer.folk.app/api-reference/companies/get-a-company /schemas/2025-06-09.json get /v1/companies/{companyId} Retrieve an existing company in the workspace. # List companies Source: https://developer.folk.app/api-reference/companies/list-companies /schemas/2025-06-09.json get /v1/companies Retrieve a list of companies in the workspace. # Update a company Source: https://developer.folk.app/api-reference/companies/update-a-company patch /v1/companies/{companyId} Update an existing company in the workspace. Update an existing company in the workspace, updating only the provided fields and leaving the rest of the company's data unchanged. Fields with a list of values (`groups`, `addresses`, `emails`, `phones`, `urls`) will **replace the old values**. This means that you must provide the entire list of values for that field, not just the values you want to add. Company names are unique across the workspace. If you try to update a company with another company's name, the request will fail. When removing a company from a group, their custom field values for that group are also removed. # Update companies in batch Source: https://developer.folk.app/api-reference/companies/update-companies-in-batch post /v1/companies/batch/update Update up to 100 existing companies in the workspace, each with the fields `PATCH /v1/companies/{companyId}` accepts. Each record is updated on its own: one that fails does not prevent the others from being updated. Fields with a list of values (`groups`, `addresses`, `emails`, `phones`, `urls`) will **replace the old values**. This means that you must provide the entire list of values for that field, not just the values you want to add. **`groups` replaces the groups of the company, and removing a group deletes data.** A group you can access that is missing from `groups` is removed from the company, together with the company's custom field values in that group. This is the opposite of `POST /v1/companies/batch/create`, where `groups` only adds. Leave `groups` out to keep the company's groups unchanged. Each result lists the groups it removed in `removedGroups`. **A name must stay unique.** A record renaming a company to the name of another company fails, as in `PATCH /v1/companies/{companyId}`, and so do all the records renaming different companies to the same name, whatever their order. A company that another record of the batch renames still holds its name when the batch is checked. The records of one company, by its ID or by the ID of a company merged into it, are merged into one update, in request order: the last value given for a field wins, and `customFieldValues` are merged group by group, the last value given for a custom field winning. The merged update is validated as a whole: if it is invalid, every record of that company fails with that error. Each of those records gets its own result, with the company as finally updated. Update up to 100 existing companies in the workspace, identified by their `id`. Each record takes the same fields as [update a company](/api-reference/companies/update-a-company), native fields and `customFieldValues` alike, and is updated on its own: a record that fails, for instance because its `id` matches no company you can access, does not prevent the others from being updated. See [batch requests](/api-reference/batch-requests) for the 100-record limit and how to read the response. Fields with a list of values (`groups`, `addresses`, `emails`, `phones`, `urls`) will **replace the old values**. This means that you must provide the entire list of values for that field, not just the values you want to add. **`groups` replaces the groups of the company, and removing a group deletes data.** A group you can access that is missing from `groups` is removed from the company, together with the company's custom field values in that group. Leave `groups` out to keep the company's groups unchanged. This is the opposite of [create companies in batch](/api-reference/companies/create-companies-in-batch), where `groups` only adds. Each succeeded result lists, in `removedGroups`, the groups the update removed the company from, empty when it removed none. ## Company names A company name is unique in the workspace, so a rename can fail: * A record renaming a company to the name of another company fails, with the same error as [update a company](/api-reference/companies/update-a-company). * Records renaming different companies to the same new name all fail, whatever their order: the batch cannot give one name to two companies. * A company keeps its current name while the batch is checked. Renaming `Acme` to `Acme Corp` and another company to `Acme` in the same batch fails the second record: send it in a later request. Names match exactly, case included: renaming a company to `acme` while another is named `Acme` succeeds. ## Records for the same company The records of one company, by its `id` or by the `id` of a company merged into it, are merged into one update, in the order you sent them: the last value given for a field wins, and `customFieldValues` are merged group by group, the last value given for a custom field winning. Two records giving the same company the same name are one rename, not a collision. The merged update is validated as a whole: if it is invalid, every record of that company fails with that error. Each of those records gets its own result, with the company as finally updated. This is not always the same as sending the records one after the other: a record setting a custom field value in a group fails, together with the other records of its company, when a later record of that company leaves the group out of `groups`, where sent first on its own it would succeed. Each updated company comes back in full, as in the response of [update a company](/api-reference/companies/update-a-company). A record that was updated but not entirely, for instance because the company could not be added to one of its groups, succeeds with a `warnings` list explaining what was not applied. # Create an object Source: https://developer.folk.app/api-reference/custom-objects/create-an-object post /v1/groups/{groupId}/{objectType} Create a new object of any type in a group. Create a new object of a given type in a group. Set the `objectType` path parameter to the name of the object type you want to create — `Deals`, `Projects`, `Applications`, or any other object type defined in the group. See [Custom objects](/api-reference/custom-objects/overview) to find the object types available in a group. The body is the same for every object type: the native fields (`name`, `people`, `companies`) plus a `customFieldValues` object holding the custom fields defined for that object type in the group. ```json Applications theme={null} { "name": "Jane Doe — Senior Engineer", "people": [{ "id": "per_5fa60242-0756-4e31-8cca-30c2c5ff1ac2" }], "customFieldValues": { "Status": "Interviewing" } } ``` Custom field names in `customFieldValues` must match the custom fields defined for that object type in the group. An unknown field name fails the whole request with a `422 Unprocessable Entity` response naming the offending field. # Create objects in batch Source: https://developer.folk.app/api-reference/custom-objects/create-objects-in-batch post /v1/groups/{groupId}/{objectType}/batch/create Create up to 100 objects of one type in a group. Create up to 100 new objects of a given type in one request. Set the `objectType` path parameter to the name of the object type you want to create — `Deals`, `Projects`, `Applications`, or any other object type defined in the group. See [Custom objects](/api-reference/custom-objects/overview) to find the object types available in a group. An object type that does not exist in the group fails the whole request with a `404`, whose message lists the object types of the group. Each record takes the same fields as [create an object](/api-reference/custom-objects/create-an-object) and is created on its own: a record that fails, for instance because of an unknown custom field, does not prevent the others from being created. See [batch requests](/api-reference/batch-requests) for the 100-record limit and how to read the response. Each object comes back in full, as in the response of [create an object](/api-reference/custom-objects/create-an-object). If the API key cannot create objects in the group, every record fails. Objects are never matched with existing ones: two records with the same name, or a record named like an existing object, create separate objects. A batch retried after a timeout or a network error creates every object again. Send an [`Idempotency-Key`](/api-reference/idempotency) header, so that a retry replays the first response instead. People and companies are given by ID, and must already be in the group: an unknown one, or one not in the group, is ignored. To link people or companies you are importing, create them first with [create people in batch](/api-reference/people/create-people-in-batch) or [create companies in batch](/api-reference/companies/create-companies-in-batch), and use the IDs they return. The objects of a request are created together with their people and companies: if that fails, every record fails and no object is created. A record that succeeded but whose custom field values could not all be set carries a `warnings` list explaining what was not applied. # Delete an object Source: https://developer.folk.app/api-reference/custom-objects/delete-an-object delete /v1/groups/{groupId}/{objectType}/{objectId} Delete an existing object of any type from a group. Delete an existing object of a given type. Set the `objectType` path parameter to the name of the object type you want to delete from — `Deals`, `Projects`, `Applications`, or any other object type defined in the group. See [Custom objects](/api-reference/custom-objects/overview) to find the object types available in a group. This action is not reversible. When removing an object from the workspace, all of its information is deleted, including its custom field values, notes, and any associations with groups, companies, and other entities. # Delete objects in batch Source: https://developer.folk.app/api-reference/custom-objects/delete-objects-in-batch post /v1/groups/{groupId}/{objectType}/batch/delete Delete up to 100 objects of one type from a group. Delete up to 100 existing objects of a given type in one request. Set the `objectType` path parameter to the name of the object type you want to delete from — `Deals`, `Projects`, `Applications`, or any other object type defined in the group. See [Custom objects](/api-reference/custom-objects/overview) to find the object types available in a group. This action is not reversible. When removing an object from the workspace, all of its information is deleted, including its custom field values, notes, and any associations with groups, companies, and other entities. Each object is deleted on its own, and the response gives the outcome of every record. See [batch requests](/api-reference/batch-requests) for the 100-record limit and how to read the response. A batch targets one object type in one group, given by the path: a record that is not an object of that type in that group fails as not found. An object type that does not exist in the group fails the whole request with a `404`, whose message lists the object types of the group. If the API key cannot delete objects in the group, every record fails. An object designated by several records is deleted once, and each of those records reports it as deleted. # Get an object Source: https://developer.folk.app/api-reference/custom-objects/get-an-object get /v1/groups/{groupId}/{objectType}/{objectId} Retrieve a single object of any type by its ID. Retrieve a single object of a given type by its ID. Set the `objectType` path parameter to the name of the object type you want to read — `Deals`, `Projects`, `Applications`, or any other object type defined in the group. See [Custom objects](/api-reference/custom-objects/overview) to find the object types available in a group. # List objects Source: https://developer.folk.app/api-reference/custom-objects/list-objects get /v1/groups/{groupId}/{objectType} Retrieve a list of objects of any type in a group. Retrieve a paginated list of objects of a given type in a group. Set the `objectType` path parameter to the name of the object type you want to read — `Deals`, `Projects`, `Applications`, or any other object type defined in the group. See [Custom objects](/api-reference/custom-objects/overview) to find the object types available in a group. # Custom objects Source: https://developer.folk.app/api-reference/custom-objects/overview One set of endpoints for every object type in a group. Groups in folk can contain any object type you define. **Deals** is the default object type, but you can create your own custom objects — Projects, Applications, Properties, and so on. Every object type shares the same API: list, create, get, update, delete, create in batch, update in batch and delete in batch under `/v1/groups/{groupId}/{objectType}`. The [Deals](/api-reference/deals/list-deals) endpoints and the ones below are the same operations. `Deals` is simply the most common value of the `objectType` path parameter. ## Available endpoints | Operation | Endpoint | | - | - | | [List objects](/api-reference/custom-objects/list-objects) | `GET /v1/groups/{groupId}/{objectType}` | | [Create an object](/api-reference/custom-objects/create-an-object) | `POST /v1/groups/{groupId}/{objectType}` | | [Get an object](/api-reference/custom-objects/get-an-object) | `GET /v1/groups/{groupId}/{objectType}/{objectId}` | | [Update an object](/api-reference/custom-objects/update-an-object) | `PATCH /v1/groups/{groupId}/{objectType}/{objectId}` | | [Delete an object](/api-reference/custom-objects/delete-an-object) | `DELETE /v1/groups/{groupId}/{objectType}/{objectId}` | | [Create objects in batch](/api-reference/custom-objects/create-objects-in-batch) | `POST /v1/groups/{groupId}/{objectType}/batch/create` | | [Update objects in batch](/api-reference/custom-objects/update-objects-in-batch) | `POST /v1/groups/{groupId}/{objectType}/batch/update` | | [Delete objects in batch](/api-reference/custom-objects/delete-objects-in-batch) | `POST /v1/groups/{groupId}/{objectType}/batch/delete` | ## Find the object types of a group The `objectType` path parameter is the name of the object type, exactly as it appears in the folk app (for example `Deals`, `Projects`, or `Applications`). The match is case-sensitive. To list the object types available in a group, call [List group custom fields](/api-reference/group-custom-fields/list-group-custom-fields) with `person` as the entity type. Every field returned with a `type` of `objectField` is an object type you can use, and its `name` is the value to pass as `objectType`. ```bash theme={null} curl -X GET "https://api.folk.app/v1/groups/grp_bc984b3f-0386-434d-82d7-a91eb6badd71/custom-fields/person" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json theme={null} { "data": { "items": [ { "name": "Deals", "type": "objectField" }, { "name": "Applications", "type": "objectField" }, { "name": "Lifecycle stage", "type": "singleSelect" } ], "pagination": {} } } ``` In the response above, both `Deals` and `Applications` can be used as `objectType`. If the object type does not exist in the group, the API returns a `404 Not Found` response whose message lists the entity types available in that group. ## Shared fields Whatever the object type, every object has the same native fields: * `name`: The name of the object. * `people`: A list of people associated with the object. * `companies`: A list of companies associated with the object. * `createdAt`: The date and time the object was created. * `createdBy`: The user who created the object. Everything else is a custom field, defined per object type in the group, and read or written through `customFieldValues`. Read more about custom fields # Update an object Source: https://developer.folk.app/api-reference/custom-objects/update-an-object patch /v1/groups/{groupId}/{objectType}/{objectId} Update an existing object of any type in a group. Update an existing object, updating only the provided fields and leaving the rest of the object's data unchanged. Set the `objectType` path parameter to the name of the object type you want to update — `Deals`, `Projects`, `Applications`, or any other object type defined in the group. See [Custom objects](/api-reference/custom-objects/overview) to find the object types available in a group. Fields with a list of values (`people`, `companies`) will **replace the old values**. This means that you must provide the entire list of values for that field, not just the values you want to add. # Create a deal Source: https://developer.folk.app/api-reference/deals/create-a-deal /schemas/2025-06-09.json post /v1/groups/{groupId}/{objectType} Create a new deal in the workspace. # Create deals in batch Source: https://developer.folk.app/api-reference/deals/create-deals-in-batch post /v1/groups/{groupId}/{objectType}/batch/create Create up to 100 new deals of one type in a group, each with the fields `POST /v1/groups/{groupId}/{objectType}` accepts. Each record is created on its own: one that fails does not prevent the others from being created. **Records are never matched with existing deals.** Two records with the same name, or a record naming an existing deal, create separate deals. A batch retried after a timeout or a network error therefore creates every deal a second time: send an `Idempotency-Key` header, so that a retry replays the first response instead. **People and companies are given by ID**, and must already be in the group: an unknown one, or one not in the group, is ignored, as in `POST /v1/groups/{groupId}/{objectType}`. To link people or companies you are importing, create them first, with [Create people in batch](/api-reference/people/create-people-in-batch) or [Create companies in batch](/api-reference/companies/create-companies-in-batch), and use the IDs they return. The deals of a request are created together with their people and companies: if that fails, every record fails and no deal is created. An object type that does not exist in the group fails the whole request with a `404`, whose message lists the object types of the group. Create up to 100 deals in a group. Each record takes the same fields as [create a deal](/api-reference/deals/create-a-deal) and is created on its own: a record that fails, for instance because of an unknown custom field, does not prevent the others from being created. See [batch requests](/api-reference/batch-requests) for the 100-record limit and how to read the response. Each deal comes back in full, as in the response of [create a deal](/api-reference/deals/create-a-deal). A batch targets one object type in one group, given by the path. An object type that does not exist in the group fails the whole request with a `404`, whose message lists the object types of the group. If the API key cannot create deals in the group, every record fails. Deals are never matched with existing ones: two records with the same name, or a record named like an existing deal, create separate deals. A batch retried after a timeout or a network error creates every deal again. Send an [`Idempotency-Key`](/api-reference/idempotency) header, so that a retry replays the first response instead. People and companies are given by ID, and must already be in the group: an unknown one, or one not in the group, is ignored. To link people or companies you are importing, create them first with [create people in batch](/api-reference/people/create-people-in-batch) or [create companies in batch](/api-reference/companies/create-companies-in-batch), and use the IDs they return. The deals of a request are created together with their people and companies: if that fails, every record fails and no deal is created. A record that succeeded but whose custom field values could not all be set carries a `warnings` list explaining what was not applied. # Delete a deal Source: https://developer.folk.app/api-reference/deals/delete-a-deal delete /v1/groups/{groupId}/{objectType}/{objectId} Delete an existing deal in the workspace. This action is not reversible. When removing a deal from the workspace, all of their information is deleted, including their custom field values, notes, and any associations with groups, companies, and other entities. # Delete deals in batch Source: https://developer.folk.app/api-reference/deals/delete-deals-in-batch post /v1/groups/{groupId}/{objectType}/batch/delete Delete up to 100 existing deals of one type in a group. Each record is deleted on its own: one that fails does not prevent the others from being deleted. This action is not reversible. When removing a deal from the workspace, all of its information is deleted, including its custom field values, notes, and any associations with groups, companies, and other entities. Each deal is deleted on its own, and the response gives the outcome of every record. See [batch requests](/api-reference/batch-requests) for the 100-record limit and how to read the response. A batch targets one object type in one group, given by the path: a record that is not a deal of that type in that group fails as not found. An object type that does not exist in the group fails the whole request with a `404`, whose message lists the object types of the group. If the API key cannot delete deals in the group, every record fails. A deal designated by several records is deleted once, and each of those records reports it as deleted. # Get a deal Source: https://developer.folk.app/api-reference/deals/get-a-deal /schemas/2025-06-09.json get /v1/groups/{groupId}/{objectType}/{objectId} Retrieve an existing deal in the workspace. # List deals Source: https://developer.folk.app/api-reference/deals/list-deals /schemas/2025-06-09.json get /v1/groups/{groupId}/{objectType} Retrieve a list of deals in the workspace. # Update a deal Source: https://developer.folk.app/api-reference/deals/update-a-deal patch /v1/groups/{groupId}/{objectType}/{objectId} Update an existing deal in the workspace. Update an existing deal in the workspace, updating only the provided fields and leaving the rest of the deal's data unchanged. Fields with a list of values (`people`, `companies`) will **replace the old values**. This means that you must provide the entire list of values for that field, not just the values you want to add. # Update deals in batch Source: https://developer.folk.app/api-reference/deals/update-deals-in-batch post /v1/groups/{groupId}/{objectType}/batch/update Update up to 100 existing deals of one type in a group, each with the fields `PATCH /v1/groups/{groupId}/{objectType}/{objectId}` accepts. Each record is updated on its own: one that fails does not prevent the others from being updated. `people` and `companies` **replace** the people and companies of the deal: you must provide the entire list, not just the ones you want to add. Leave them out to keep them unchanged. They are given by ID, and an unknown one, or one not in the group, is ignored, as in `PATCH /v1/groups/{groupId}/{objectType}/{objectId}`. A record whose ID designates an object of another type, or of another group, fails as not found. The records of one deal are merged into one update, in request order: the last value given for a field wins, and `customFieldValues` are merged field by field, the last value given for a custom field winning. The merged update is validated as a whole: if it is invalid, every record of that deal fails with that error. Each of those records gets its own result, with the deal as finally updated. An object type that does not exist in the group fails the whole request with a `404`, whose message lists the object types of the group. Update up to 100 existing deals in a group, identified by their `id`. Each record takes the same fields as [update a deal](/api-reference/deals/update-a-deal) and is updated on its own: a record that fails, for instance because of an unknown custom field, does not prevent the others from being updated. See [batch requests](/api-reference/batch-requests) for the 100-record limit and how to read the response. Each deal comes back in full, as in the response of [update a deal](/api-reference/deals/update-a-deal). A batch targets one object type in one group, given by the path. An object type that does not exist in the group fails the whole request with a `404`, whose message lists the object types of the group. A record whose `id` designates an object of another type, or of another group, fails as not found. If the API key cannot update deals in the group, every record fails. `people` and `companies` **replace** the people and companies of the deal: you must provide the entire list, not just the ones you want to add. Leave them out to keep them unchanged. People and companies are given by ID, and must already be in the group: an unknown one, or one not in the group, is ignored. The records of one deal are merged into one update, in the order you sent them: the last value given for a field wins, and `customFieldValues` are merged field by field, the last value given for a custom field winning. The merged update is validated as a whole: if it is invalid, every record of that deal fails with that error. Each of those records gets its own result, with the deal as finally updated. This is not always the same as sending the records one after the other: a record with an invalid custom field value fails every record of its deal, including the earlier ones, which sent on their own would have been applied before it. A record that succeeded but whose people, companies or custom field values could not all be set carries a `warnings` list explaining what was not applied. # Errors Source: https://developer.folk.app/api-reference/errors folk uses conventional HTTP response codes to indicate the success or failure of an API request. In general: Codes in the `2xx` range indicate success. Codes in the `4xx` range indicate an error that failed given the information provided (e.g., a required parameter was omitted, a charge failed, etc.). Codes in the `5xx` range indicate an error with folk's servers. ## Error response The error response contains a single `error` object that includes the following properties: * `code`: The code that identifies the error. * `message`: A human-readable description of the error. * `documentationUrl`: An URL to the documentation that describes the error. * `timestamp`: The timestamp when the error occurred. * `requestId`: [The request identifier](/api-reference/request-id). ```json theme={null} { "error": { "code": "RESOURCE_NOT_FOUND", "message": "The requested resource was not found.", "documentationUrl": "https://developer.folk.app/api-reference/errors#not-found", "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z" } } ``` Some `4xx` errors that could be handled programmatically (e.g., validation errors) include a `details` property that provides more information about the error. Refer to each specific error in order to see if a `details` property is available and its shape. ```json {8-14} theme={null} { "error": { "code": "TOO_MANY_REQUESTS", "message": "You have exceeded your rate limit.", "documentationUrl": "https://developer.folk.app/api-reference/errors#rate-limiting", "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z", "details": { "policy": "burst", "limit": 20, "remaining": 0, "windowSeconds": 2, "retryAfter": "2025-10-01T12:00:02.000Z" } } } ``` ## Error list The errors that could be returned by folk's API are the following: #### Bad request Status code: `400`
The request was malformed or invalid. It is also returned with the `IDEMPOTENCY_KEY_INVALID` code when the `Idempotency-Key` header is malformed. See the [idempotency section](/api-reference/idempotency#errors). ```json theme={null} { "error": { "code": "INVALID_REQUEST", "message": "The request was invalid.", "documentationUrl": "https://developer.folk.app/api-reference/errors#bad-request", "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z" } } ``` #### Unauthorized Status code: `401`
Access to the resource is unauthorized. This error is usually returned when the API key is invalid or not provided. Always make sure to provide an API key in the `Authorization` header.
Read more about authentication in the [authentication section](/api-reference/authentication). ```json theme={null} { "error": { "code": "UNAUTHORIZED", "message": "No valid API key provided.", "documentationUrl": "https://developer.folk.app/api-reference/errors#unauthorized", "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z" } } ``` #### Forbidden Status code: `403`
Access to the resource is forbidden. This error is usually returned when the API key doesn't have permissions to perform the operation. ```json theme={null} { "error": { "code": "FORBIDDEN", "message": "The API key doesn’t have permissions to perform the request.", "documentationUrl": "https://developer.folk.app/api-reference/errors#forbidden", "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z" } } ``` #### Not found Status code: `404`
The requested resource was not found. This error is usually returned when a resource that is being requested doesn't exist. ```json theme={null} { "error": { "code": "RESOURCE_NOT_FOUND", "message": "The requested resource was not found.", "documentationUrl": "https://developer.folk.app/api-reference/errors#not-found", "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z" } } ``` #### Conflict Status code: `409`
A request with the same `Idempotency-Key` is still being processed. Retry the request once the first one has completed. Read more in the [idempotency section](/api-reference/idempotency). ```json theme={null} { "error": { "code": "IDEMPOTENCY_REQUEST_IN_PROGRESS", "message": "A request with the same Idempotency-Key is still being processed. Retry once it has completed.", "documentationUrl": "https://developer.folk.app/api-reference/idempotency#errors", "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z" } } ``` #### Unprocessable entity Status code: `422`
The request contains invalid data. This error is usually returned when there is a validation error in the body or query parameters of the request. It is also returned with the `IDEMPOTENCY_KEY_REUSED` code when an `Idempotency-Key` is reused for a different request, and with the `IDEMPOTENCY_RESPONSE_NOT_STORED` code when a retried request already ran but its response could not be saved, because it was too large or not JSON text. See the [idempotency section](/api-reference/idempotency#errors). The `details` object includes an array of `issues` describing each validation rule that has failed validation. You can find an exaustive list of all available properties on the official [Zod documentation](https://v3.zod.dev/ERROR_HANDLING?id=zodissue). ```json theme={null} { "error": { "code": "UNPROCESSABLE_ENTITY", "message": "Invalid query parameters", "documentationUrl": "https://developer.folk.app/api-reference/errors#unprocessable-entity", "details": { "issues": [ { "code": "too_small", "minimum": 1, "type": "number", "inclusive": true, "exact": false, "message": "Number must be greater than or equal to 1", "path": ["limit"] } ] }, "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z" } } ``` #### Rate limiting Status code: `429`
A rate limit policy has been exceeded. The `details` object describes the policy that was exceeded. When several policies are exceeded, it describes the one that resets last. It includes the following properties: * `policy`: The name of the exceeded policy: `default` or `burst`. * `limit`: The maximum number of requests allowed per window by this policy. * `remaining`: The number of requests remaining in the current window. Always `0` for the exceeded policy. * `windowSeconds`: The length of the policy's window, in seconds. * `retryAfter`: The ISO 8601 Datetime when the policy's window resets and you can retry. * `policies`: The status of every policy applied to the request, each with the same properties as above. Read more about rate limits in the [rate limits section](/api-reference/rate-limits). ```json theme={null} { "error": { "code": "TOO_MANY_REQUESTS", "message": "You have exceeded your rate limit.", "documentationUrl": "https://developer.folk.app/api-reference/errors#rate-limiting", "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z", "details": { "policy": "burst", "limit": 20, "remaining": 0, "windowSeconds": 2, "retryAfter": "2025-10-01T12:00:02.000Z", "policies": [ { "policy": "default", "limit": 600, "remaining": 412, "windowSeconds": 60, "retryAfter": "2025-10-01T12:01:00.000Z" }, { "policy": "burst", "limit": 20, "remaining": 0, "windowSeconds": 2, "retryAfter": "2025-10-01T12:00:02.000Z" } ] } } } ``` #### Internal server error Status code: `500`
An error occurred on the server. If this error is returned, it means folk's servers encountered an error while processing the request.
If the error persists, please contact support and include the `requestId` shown in the response. ```json theme={null} { "error": { "code": "INTERNAL_SERVER_ERROR", "message": "An internal server error occurred.", "documentationUrl": "https://developer.folk.app/api-reference/errors#internal-server-error", "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z" } } ``` #### Service unavailable Status code: `503`
The service is temporarily unavailable. If this error is returned, it means folk's servers are temporarily unavailable.
folk strives to keep the service available at all times, but sometimes it's necessary to perform maintenance or upgrades. If the error persists, please contact support and include the `requestId` shown in the response. It is also returned with the `IDEMPOTENCY_UNAVAILABLE` code when folk cannot guarantee idempotency for a request sent with an `Idempotency-Key`. The request was not processed: retry it with the same key. See the [idempotency section](/api-reference/idempotency#errors). ```json theme={null} { "error": { "code": "SERVICE_UNAVAILABLE", "message": "The service is currently unavailable.", "documentationUrl": "https://developer.folk.app/api-reference/errors#service-unavailable", "requestId": "123e4567-e89b-12d3-a456-426614174000", "timestamp": "2025-10-01T12:00:00Z" } } ``` # Filtering Source: https://developer.folk.app/api-reference/filtering Some top level API resources can be filtered when listing them. Using filtering is a faster alternative to paginating through all resources. To create a search query, review the filter syntax and reference the filterable fields of the resource. ## Examples ### People search Listing people by name ```http theme={null} GET /v1/people?filter[fullName][eq]=John Doe ``` Listing people by email domain or with empty emails ```http theme={null} GET /v1/people?filter[emails][like]=@folk.app&filter[emails][empty]&combinator=or ``` Listing people of a specific gender. Authorised values are 'Male', 'Female', 'Other', 'Unknown' ```http theme={null} GET /v1/people?filter[gender][eq]=Female ``` Listing people not in any group ```http theme={null} GET /v1/people?filter[groups][empty] ``` Listing people in some group ```http theme={null} GET /v1/people?filter[groups][in][id]=grp_79b6ed73-9939-4118-ba65-7f8cdf401052 ``` Listing people in all listed groups ```http theme={null} GET /v1/people?filter[groups][all][id]=grp_79b6ed73-9939-4118-ba65-7f8cdf401052&filter[groups][all][id]=grp_f19d5fa3-6ffb-4f1f-9298-96c19f222e2e ``` Listing people in a company ```http theme={null} GET /v1/people?filter[companies][in][id]=com_f3855422-6740-4e6a-aa5e-449ac77047d0 ``` Listing people the team last interacted with after a given date ```http theme={null} GET /v1/people?filter[interactionMetadata.workspace.lastInteractedAt][gt]=2025-01-01 ``` Listing people you last interacted with after a given date ```http theme={null} GET /v1/people?filter[interactionMetadata.user.lastInteractedAt][gt]=2025-01-01 ``` Listing people assigned to some users ```http theme={null} GET /v1/people?filter[customFieldValues.grp_79b6ed73-9939-4118-ba65-7f8cdf401052.Owner][in][email]=john@folk.app&filter[customFieldValues.grp_79b6ed73-9939-4118-ba65-7f8cdf401052.Owner][in][email]=alice@folk.app ``` Listing people associated to some contacts ```http theme={null} GET /v1/people?filter[customFieldValues.grp_79b6ed73-9939-4118-ba65-7f8cdf401052.Relationships][in][id]=per_b6b9edb9-166d-4bdf-a0bf-8d7dac1de98a&filter[customFieldValues.grp_79b6ed73-9939-4118-ba65-7f8cdf401052.Relationships][in][id]=com_ee7c7212-99ba-4e8d-bd14-9ff369c87a1c ``` ### Companies search Listing companies by name ```http theme={null} GET /v1/companies?filter[name][eq]=ECMA ``` Listing companies not in any group ```http theme={null} GET /v1/companies?filter[groups][empty] ``` ### Deals search Listing deals by name ```http theme={null} GET /v1/groups/grp_bc984b3f-0386-434d-82d7-a91eb6badd71/Deals?filter[name][eq]=Project Alpha ``` Listing deals with the status "Qualified" ```http theme={null} GET /v1/groups/grp_bc984b3f-0386-434d-82d7-a91eb6badd71/Deals?filter[customFieldValues.Status][in]=Qualified ``` ### Tasks search Listing tasks due after a given date ```http theme={null} GET /v1/tasks?filter[dueAt][gt]=2025-01-01 ``` Listing tasks not yet completed ```http theme={null} GET /v1/tasks?filter[completedAt][empty] ``` Listing tasks assigned to some users ```http theme={null} GET /v1/tasks?filter[assigneeUserId][in]=usr_79b6ed73-9939-4118-ba65-7f8cdf401052 ``` Listing tasks linked to a person ```http theme={null} GET /v1/tasks?filter[entity][in]=per_f3855422-6740-4e6a-aa5e-449ac77047d0 ``` ## Filter syntax The filter syntax allows you to specify conditions for the fields you want to filter by. The syntax is as follows: `?filter[][]=` Where: * `` is the field we want to filter on * `` is the operation we want to perform * `` is the value we're filtering against ### Operators The following operators are available for filtering: | Operator | Meaning | Applicable to | | - | - | - | | `eq` | field is equal to the value | Text, numbers, and dates fields | | `not_eq` | field is not equal to the value | Text, numbers, and dates fields | | `gt` | field is greater than the value | Numbers and dates fields | | `lt` | field is less than the value | Numbers and dates fields | | `like` | field matches the value | Text | | `not_like` | field does not match the value | Text | | `all` | field is equal to the list of values | Multi reference fields | | `in` | field is in the list of values | Single and multi reference fields | | `not_in` | field is not in the list of values | Single and multi reference fields | | `empty` | field is empty | All fields | | `not_empty` | field is not empty | All fields | ## Filterable fields ### Filterable fields for people | Field | Usage | Supported operators | | - | - | - | | `fullName` | `?filter[fullName][like]=John` | `eq`, `not_eq`, `like`, `not_like`, `empty`, `not_empty` | | `firstName` | `?filter[firstName][eq]=John` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `lastName` | `?filter[lastName][not_eq]=Doe` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `jobTitle` | `?filter[jobTitle][like]=Developer` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `birthday` | `?filter[birthday][gt]=1990-01-01` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `description` | `?filter[description][like]=team player` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `emails` | `?filter[emails][like]=@folk.app` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `phones` | `?filter[phones][eq]=+1234567890` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `addresses` | `?filter[addresses][not_like]=Paris` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `urls` | `?filter[urls][not_eq]=https://example.com` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `companies` | `?filter[companies][in][id]=com_c5993dec-0375-407e-9e0a-a310d39f65bd` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `groups` | `?filter[groups][in][id]=grp_c5993dec-0375-407e-9e0a-a310d39f65bd` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `createdAt` (day precision) | `?filter[createdAt][gt]=2020-01-01` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `createdBy` | `?filter[createdBy][in][email]=john@folk.app&filter[createdBy][in][id]=usr_c5993dec-0375-407e-9e0a-a310d39f65bd` | `in`, `not_in`, `empty`, `not_empty` | | `interactionMetadata.workspace.lastInteractedAt` (day precision) | `?filter[interactionMetadata.workspace.lastInteractedAt][gt]=2025-01-01` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `interactionMetadata.user.lastInteractedAt` (day precision) | `?filter[interactionMetadata.user.lastInteractedAt][gt]=2025-01-01`

This field is scoped to the user behind the API key. The same request made with API keys from different workspace users can return different results. | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Text` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Text][like]=team player` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `customFieldValues.{groupId}.Date` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Date][gt]=2020-01-01` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Number` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Number][lt]=200` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Status` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Status][in]=In Progress` | `in`, `not_in`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Tags` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Tags][in]=B2B&filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Tags][in]=B2C` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Relationships` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Relationships][in][id]=per_a858d1bd-19d3-486d-87a4-c3b13d5b9a99` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Assign` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Assign][in][email]=john@folk.app&filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Assign][in][id]=usr_d24b4748-8a5f-4a46-89fe-f19cff553a22` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Deals` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Deals][in][id]=obj_5ef2bef1-5234-4e09-bc15-f830697604b8` | `all`, `in`, `not_in`, `empty`, `not_empty` | ### Filterable fields for companies | Field | Usage | Supported operators | | - | - | - | | `name` | `?filter[name][like]=ECMA` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `description` | `?filter[description][like]=prospect` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `emails` | `?filter[emails][like]=@folk.app` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `phones` | `?filter[phones][eq]=+1234567890` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `addresses` | `?filter[addresses][not_like]=Paris` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `urls` | `?filter[urls][not_eq]=https://example.com` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `groups` | `?filter[groups][in][id]=grp_c5993dec-0375-407e-9e0a-a310d39f65bd` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `createdAt` (day precision) | `?filter[createdAt][gt]=2020-01-01` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `createdBy` | `?filter[createdBy][in][email]=john@folk.app&filter[createdBy][in][id]=usr_c5993dec-0375-407e-9e0a-a310d39f65bd` | `in`, `not_in`, `empty`, `not_empty` | | `fundingRaised` | `?filter[fundingRaised][gt]=2000000` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `lastFundingDate` | `?filter[lastFundingDate][gt]=2020-01-01` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `foundationYear` | `?filter[foundationYear][gt]=1999` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `industry` | `?filter[industry][like]=tech` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `employeeRange` | `?filter[employeeRange][like]=tech` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `customFieldValues.{groupId}.Text` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Text][like]=team player` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `customFieldValues.{groupId}.Date` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Date][gt]=2020-01-01` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Number` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Number][lt]=200` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Status` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Status][in]=In Progress` | `in`, `not_in`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Tags` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Tags][in]=B2B&filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Tags][in]=B2C` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Relationships` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Relationships][in][id]=per_a858d1bd-19d3-486d-87a4-c3b13d5b9a99` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Assign` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Assign][in][email]=john@folk.app&filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Assign][in][id]=usr_d24b4748-8a5f-4a46-89fe-f19cff553a22` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `customFieldValues.{groupId}.Deals` | `?filter[customFieldValues.grp_c5993dec-0375-407e-9e0a-a310d39f65bd.Deals][in][id]=obj_5ef2bef1-5234-4e09-bc15-f830697604b8` | `all`, `in`, `not_in`, `empty`, `not_empty` | ### Filterable fields for deals | Field | Usage | Supported operators | | - | - | - | | `name` | `?filter[name][like]=Project Alpha` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `people` | `?filter[people][in][id]=per_a858d1bd-19d3-486d-87a4-c3b13d5b9a99` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `companies` | `?filter[companies][in][id]=com_c5993dec-0375-407e-9e0a-a310d39f65bd` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `createdAt` (day precision) | `?filter[createdAt][gt]=2020-01-01` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `createdBy` | `?filter[createdBy][in][email]=john@folk.app&filter[createdBy][in][id]=usr_c5993dec-0375-407e-9e0a-a310d39f65bd` | `in`, `not_in`, `empty`, `not_empty` | | `customFieldValues.Text` | `?filter[customFieldValues.Text][like]=team player` | `eq`, `not_eq`, `like`, `not_like`,`empty`, `not_empty` | | `customFieldValues.Date` | `?filter[customFieldValues.Date][gt]=2020-01-01` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `customFieldValues.Number` | `?filter[customFieldValues.Number][lt]=200` | `eq`, `not_eq`, `gt`, `lt`, `empty`, `not_empty` | | `customFieldValues.Status` | `?filter[customFieldValues.Status][in]=In Progress` | `in`, `not_in`, `empty`, `not_empty` | | `customFieldValues.Tags` | `?filter[customFieldValues.Tags][in]=B2B&filter[customFieldValues.Tags][in]=B2C` | `all`, `in`, `not_in`, `empty`, `not_empty` | | `customFieldValues.Assign` | `?filter[customFieldValues.Assign][in][email]=john@folk.app&filter[customFieldValues.Assign][in][id]=usr_d24b4748-8a5f-4a46-89fe-f19cff553a22` | `all`, `in`, `not_in`, `empty`, `not_empty` | ### Filterable fields for tasks | Field | Usage | Supported operators | | - | - | - | | `dueAt` | `?filter[dueAt][gt]=2025-01-01` | `eq`, `not_eq`, `gt`, `lt` | | `createdAt` | `?filter[createdAt][gt]=2025-01-01` | `gt`, `lt` | | `assigneeUserId` | `?filter[assigneeUserId][in]=usr_79b6ed73-9939-4118-ba65-7f8cdf401052` | `in`, `not_in` | | `entity` | `?filter[entity][in]=per_f3855422-6740-4e6a-aa5e-449ac77047d0` | `in`, `not_in` | | `completedAt` | `?filter[completedAt][empty]` | `empty`, `not_empty`, `gt`, `lt` | # Create a group custom field Source: https://developer.folk.app/api-reference/group-custom-fields/create-a-group-custom-field /schemas/2025-06-09.json post /v1/groups/{groupId}/custom-fields/{entityType} Creates a new group custom field for an entity type. # Get a group custom field Source: https://developer.folk.app/api-reference/group-custom-fields/get-a-group-custom-field /schemas/2025-06-09.json get /v1/groups/{groupId}/custom-fields/{entityType}/{customFieldName} Returns a detailed group custom field for an entity type. # List group custom fields Source: https://developer.folk.app/api-reference/group-custom-fields/list-group-custom-fields /schemas/2025-06-09.json get /v1/groups/{groupId}/custom-fields/{entityType} Returns a list of group custom fields for an entity type. # Update a group custom field Source: https://developer.folk.app/api-reference/group-custom-fields/update-a-group-custom-field /schemas/2025-06-09.json patch /v1/groups/{groupId}/custom-fields/{entityType}/{customFieldName} Updates a group custom field for an entity type. # Add a group member Source: https://developer.folk.app/api-reference/group-members/add-a-group-member /schemas/2025-06-09.json post /v1/groups/{groupId}/members Adds a member to a group. # List group members Source: https://developer.folk.app/api-reference/group-members/list-group-members /schemas/2025-06-09.json get /v1/groups/{groupId}/members Returns a list of the group members. For a public group, all workspace members are members of the group. # Remove a group member Source: https://developer.folk.app/api-reference/group-members/remove-a-group-member /schemas/2025-06-09.json delete /v1/groups/{groupId}/members/{userId} Removes a member from a group. # Update a group member Source: https://developer.folk.app/api-reference/group-members/update-a-group-member /schemas/2025-06-09.json patch /v1/groups/{groupId}/members/{userId} Updates a group member's role. # Create a group Source: https://developer.folk.app/api-reference/groups/create-a-group /schemas/2025-06-09.json post /v1/groups Create a new group in the workspace. A default "All people" table view is automatically created for the group. # List groups Source: https://developer.folk.app/api-reference/groups/list-groups /schemas/2025-06-09.json get /v1/groups Returns a list of workspace groups. # Update a group Source: https://developer.folk.app/api-reference/groups/update-a-group /schemas/2025-06-09.json patch /v1/groups/{groupId} Update a group's name and/or visibility. # Idempotency Source: https://developer.folk.app/api-reference/idempotency folk's API supports idempotency, so you can safely retry a request that creates, updates or deletes data without running the operation twice. This is useful when a request times out or the connection drops before you receive the response: you cannot tell whether it went through, but you can retry it with the same key. ## Sending an idempotency key Add an `Idempotency-Key` header to any `POST`, `PATCH`, `PUT` or `DELETE` request. The key is a value you generate, unique to the operation you want to perform. We recommend a UUID v4. ```bash theme={null} curl -X POST "https://api.folk.app/v1/people" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324" \ -d '{"firstName": "John", "lastName": "Doe"}' ``` Keys are 1 to 255 printable ASCII characters, other than space. The header is optional: requests without it behave as usual. It is ignored on `GET` requests, which never change data. ## How retries are handled The first request with a given key runs normally, and its response is saved, whatever its status code, including errors. Responses larger than 1 MiB are not saved, nor are responses that cannot be saved as JSON text, such as streamed or binary ones. A retry of such a request is rejected with `IDEMPOTENCY_RESPONSE_NOT_STORED` rather than run again: the operation already ran. Any later request with the same key and the same body returns the saved response, with the same status code and body, without running the operation again. A replay returns the saved response once your API key and workspace membership are checked again, but without checking your access to individual groups again, as the response belongs to an operation you were allowed to run. A replayed response carries an `Idempotent-Replayed: true` header. Its body is the original one, including the original `requestId`. Of the original headers, only `Content-Type`, `Deprecation` and `Sunset` are replayed. The others describe the retry: its `X-Request-Id` header identifies the retry, and its rate limit headers reflect your current usage. Keys are scoped to your user and workspace, and are kept for **24 hours** after the request completes. Once a key has expired, a request with that key runs as a new request. While a request is still being processed, its key is held for up to 5 minutes. If its response could not be saved, for instance because of a server failure, the key is freed after that delay and a retry runs the operation again. A key is tied to the request it was first used with: its method, route and path parameters, query parameters, API version and body. The order of keys in the JSON body does not matter, and a request without a body is treated like one with an empty `{}` body. Reusing a key for a different request returns an `IDEMPOTENCY_KEY_REUSED` error instead of replaying the earlier response. Responses to requests rejected before they run are not saved: authentication errors, [rate limit errors](/api-reference/rate-limits), and validation errors for an unsupported API version or an invalid body, query or path parameter. You can fix the request and retry it with the same key. ## Retrying safely We recommend the following retry strategy: 1. Generate a new key for every operation, and keep it with the request until you get a final response. 2. On a timeout, a network error, a `409` or a `5xx` error other than `500`, retry the same request with the **same** key, using exponential backoff. 3. On a `429`, wait for the delay given by the `Retry-After` header, then retry with the same key. Rate-limited requests never run. 4. A `500` returned once the operation has started is saved like any other response, so retrying with the same key returns it again. Check whether the operation took effect before sending it again with a new key. 5. On `IDEMPOTENCY_RESPONSE_NOT_STORED`, the operation already ran: check its outcome, for instance by listing the records it created, before sending it again with a new key. 6. To send the same payload again as a new operation, use a new key. ## Errors #### Invalid key Status code: `400`
Code: `IDEMPOTENCY_KEY_INVALID` The `Idempotency-Key` header is empty, sent more than once, longer than 255 characters, or contains a space or a character outside printable ASCII. #### Request in progress Status code: `409`
Code: `IDEMPOTENCY_REQUEST_IN_PROGRESS` A request with the same key is still being processed. Retry once it has completed. #### Key reused Status code: `422`
Code: `IDEMPOTENCY_KEY_REUSED` The key was already used for a different request. Use a new key. #### Response not stored Status code: `422`
Code: `IDEMPOTENCY_RESPONSE_NOT_STORED` The request with this key already ran, but its response was not kept: it was larger than 1 MiB, or could not be saved as JSON text. Check the outcome of the operation before sending it again with a new key. #### Idempotency unavailable Status code: `503`
Code: `IDEMPOTENCY_UNAVAILABLE` folk could not claim the key, so the request was **not** processed rather than processed without protection. Retry with the same key. # Create an interaction Source: https://developer.folk.app/api-reference/interactions/create-an-interaction /schemas/2025-06-09.json post /v1/interactions Creates a new [interaction](https://help.folk.app/en/articles/7012167-log-a-new-interaction) with a person or a company. # (Beta) Delete an interaction Source: https://developer.folk.app/api-reference/interactions/delete-an-interaction /schemas/2025-06-09.json delete /v1/interactions/{interactionId} Delete an existing logged interaction. Imported interactions cannot be deleted.
This endpoint is currently in open beta. The API surface may change as we iterate based on feedback. # (Beta) Get a past or upcoming interaction Source: https://developer.folk.app/api-reference/interactions/get-a-past-or-upcoming-interaction /schemas/2025-06-09.json get /v1/interactions/{interactionId} Retrieves a single interaction. Full email or calendar body is included when privacy rules allow it.
This endpoint is currently in open beta. The API surface may change as we iterate based on feedback. # (Beta) List interactions Source: https://developer.folk.app/api-reference/interactions/list-interactions /schemas/2025-06-09.json get /v1/interactions Retrieves paginated past or upcoming interactions linked to a person, company, or object, optionally filtered by imported interaction type and workspace member.
This endpoint is currently in open beta. The API surface may change as we iterate based on feedback. # (Beta) List past interactions Source: https://developer.folk.app/api-reference/interactions/list-past-interactions /schemas/2025-06-09.json get /v1/interactions/past Retrieves paginated past interactions linked to a person, company, or object.
This endpoint is currently in open beta. The API surface may change as we iterate based on feedback.
This endpoint is deprecated since September 29, 2026 and will be removed on March 29, 2027. Please use the List interactions endpoint instead. # (Beta) List upcoming interactions Source: https://developer.folk.app/api-reference/interactions/list-upcoming-interactions /schemas/2025-06-09.json get /v1/interactions/upcoming Retrieves paginated upcoming interactions linked to a person, company, or object.
This endpoint is currently in open beta. The API surface may change as we iterate based on feedback.
This endpoint is deprecated since September 29, 2026 and will be removed on March 29, 2027. Please use the List interactions endpoint instead. # (Beta) Update an interaction Source: https://developer.folk.app/api-reference/interactions/update-an-interaction /schemas/2025-06-09.json patch /v1/interactions/{interactionId} Partially update an existing logged interaction (title, content, date-time, activityType). Imported interactions cannot be updated.
This endpoint is currently in open beta. The API surface may change as we iterate based on feedback. # Create a note Source: https://developer.folk.app/api-reference/notes/create-a-note post /v1/notes Create a new note in the workspace. Create a note linked to a person, company or deal. ### Visibility The visibility of the note can be set to `public` or `private`. * `public`: The note is visible to all users in the workspace. * `private`: The note is visible only to the current user. ### Content The content of the note can be in plain text or in markdown format. We support the full CommonMark [basic syntax](https://www.markdownguide.org/basic-syntax/), including: * Headings * Bold * Italic * Lists * Links * Images * Blockquotes * Horizontal rules * Code #### Mentions We also support mentioning workspace members using a simple markdown link to the [GET /v1/users/\{userId} endpoint](/api-reference/users/get-a-user). ```markdown theme={null} [](https://api.folk.app/v1/users/) ``` For example, to mention user `John Doe` with ID `usr_6526333c-1502-46e3-aa01-8f6130f69814`: ```markdown theme={null} This is a note mentioning [John Doe](https://api.folk.app/v1/users/usr_6526333c-1502-46e3-aa01-8f6130f69814) ``` To retrieve the user ID, you can use the [GET /v1/users endpoint](/api-reference/users/list-users). You can use any value as the user name. Our system will always replace it with the real user name of the workspace member mentioned. Providing an invalid link, a wrong user id, or a user id that does not exist will not trigger any error. Always respect the syntax and use ids retrieved from the API to make sure the mention is valid and the workspace member is notified. #### Attachments We currently do not support direct attachment upload. However, you can use any bucket storage service to upload files and reference them in the note content using a link, as long as the file is publicly accessible. Example: ```markdown theme={null} [PDF link](https://my-bucket.s3.eu-west-1.amazonaws.com/my-file.pdf) ``` # Delete a note Source: https://developer.folk.app/api-reference/notes/delete-a-note /schemas/2025-06-09.json delete /v1/notes/{noteId} Delete an existing note in the workspace. # Get a note Source: https://developer.folk.app/api-reference/notes/get-a-note /schemas/2025-06-09.json get /v1/notes/{noteId} Retrieve an existing note in the workspace. # List notes Source: https://developer.folk.app/api-reference/notes/list-notes /schemas/2025-06-09.json get /v1/notes Retrieve a list of notes in the workspace. # Update a note Source: https://developer.folk.app/api-reference/notes/update-a-note patch /v1/notes/{noteId} Update an existing note in the workspace. Update an existing note linked to a person, company or deal, updating only the provided fields and leaving the rest of the note's data unchanged. ### Visibility The visibility of the note can be set to `public` or `private`. * `public`: The note is visible to all users in the workspace. * `private`: The note is visible only to the current user. ### Content The content of the note can be in plain text or in markdown format. We support the full CommonMark [basic syntax](https://www.markdownguide.org/basic-syntax/), including: * Headings * Bold * Italic * Lists * Links * Images * Blockquotes * Horizontal rules * Code #### Mentions We also support mentioning workspace members using a simple markdown link to the [GET /v1/users/\{userId} endpoint](/api-reference/users/get-a-user). ```markdown theme={null} [](https://api.folk.app/v1/users/) ``` For example, to mention user `John Doe` with ID `usr_6526333c-1502-46e3-aa01-8f6130f69814`: ```markdown theme={null} This is a note mentioning [John Doe](https://api.folk.app/v1/users/usr_6526333c-1502-46e3-aa01-8f6130f69814) ``` To retrieve the user ID, you can use the [GET /v1/users endpoint](/api-reference/users/list-users). You can use any value as the user name. Our system will always replace it with the real user name of the workspace member mentioned. Providing an invalid link, a wrong user id, or a user id that does not exist will not trigger any error. Always respect the syntax and use ids retrieved from the API to make sure the mention is valid and the workspace member is notified. #### Attachments We currently do not support direct attachment upload. However, you can use any bucket storage service to upload files and reference them in the note content using a link, as long as the file is publicly accessible. Example: ```markdown theme={null} [PDF link](https://my-bucket.s3.eu-west-1.amazonaws.com/my-file.pdf) ``` # Overview Source: https://developer.folk.app/api-reference/overview folk exposes a REST API that is organized around predictable resource-oriented URLs, accepts and returns JSON-encoded payloads, and uses standard HTTP response codes, authentication, and verbs. The base URL for the API is ``` https://api.folk.app ``` All API requests must be made over HTTPS. Calls made over plain HTTP will fail. # Pagination Source: https://developer.folk.app/api-reference/pagination Some of the top-level API resources have support for bulk fetches through “list” API methods. For example, you can list groups or list workspace users. These list API methods share a common structure and accept, at a minimum, the two parameters: `limit` and `cursor`. folk's list API methods use an opaque cursor-based pagination through the `cursor` parameter. When you perform the first request on a paginated resource, you can receive a `pagination.nextLink` value in the response. This value contains the full URL of the next page of results. You can either use this URL directly or pick the `cursor` parameter from the URL and use it in the next request.
If there are no more pages of results, the `pagination.nextLink` will be omitted from the response. The `limit` parameter is used to limit the number of objects returned in the response. The maximum number of objects you can return in a single request and the default value of this parameter depends on the resource requested. Check the documentation of the endpoint you are requesting for more information about the default and maximum values. #### Example Initial request to list groups: ```http theme={null} GET https://api.folk.app/v1/groups ``` ```json theme={null} { "data": { "items": [ ... ], "pagination": { "nextLink": "https://api.folk.app/v1/groups?limit=20&cursor=eyJvZmZzZXQiOjIwfQ%3D%3D" } } } ``` Use the `nextLink` value to fetch the next page of results: ```http theme={null} GET https://api.folk.app/v1/groups?limit=20&cursor=eyJvZmZzZXQiOjIwfQ%3D%3D ``` ```json theme={null} { "data": { "items": [ ... ], "pagination": {} } } ``` No `nextLink` value means there are no more pages to fetch. # Create a person Source: https://developer.folk.app/api-reference/people/create-a-person post /v1/people Create a new person in the workspace. Create a person in the workspace, optionally adding them to one or more groups. In the app, people without groups will only be visible through the "Search" section. folk automatically checks for duplicates when creating people. If the system detects a duplicate, the newly created person will be merged in the background, possibly overwriting some of the provided data. # Create people in batch Source: https://developer.folk.app/api-reference/people/create-people-in-batch post /v1/people/batch/create Create up to 100 new people in the workspace. Each record is created on its own: one that fails does not prevent the others from being created. Create up to 100 people in the workspace, each optionally added to one or more groups. Each record takes the same fields as [create a person](/api-reference/people/create-a-person) and is created on its own: a record that fails, for instance because it names a company ID that does not exist, does not prevent the others from being created. See [batch requests](/api-reference/batch-requests) for the 100-record limit and how to read the response. Every record creates a new person, even when two records are identical or match an existing person. A company given by `name` is linked to the existing company of that name, or created if there is none. Records naming the same new company are all linked to one created company. Names match exactly, so `Acme` and `acme` are two companies. Each created person comes back in full, as in the response of [create a person](/api-reference/people/create-a-person). A record that was created but could not be fully set up, for instance because the person could not be added to one of its groups, succeeds with a `warnings` list explaining what was not applied. folk automatically checks for duplicates when creating people. If the system detects a duplicate, the newly created person will be merged in the background, possibly overwriting some of the provided data. # Delete a person Source: https://developer.folk.app/api-reference/people/delete-a-person delete /v1/people/{personId} Delete an existing person in the workspace. This action is not reversible. When removing a person from the workspace, all of their information is deleted, including their custom field values, notes, and any associations with groups, companies, and other entities. # Delete people in batch Source: https://developer.folk.app/api-reference/people/delete-people-in-batch post /v1/people/batch/delete Delete up to 100 existing people in the workspace. Each record is deleted on its own: one that fails does not prevent the others from being deleted. This action is not reversible. When removing a person from the workspace, all of their information is deleted, including their custom field values, notes, and any associations with groups, companies, and other entities. Each person is deleted on its own, and the response gives the outcome of every record. See [batch requests](/api-reference/batch-requests) for the 100-record limit and how to read the response. A person designated by several records, by the same ID or through the ID of a person merged into it, is deleted once, and each of those records reports it as deleted. # Get a person Source: https://developer.folk.app/api-reference/people/get-a-person /schemas/2025-06-09.json get /v1/people/{personId} Retrieve an existing person in the workspace. # List people Source: https://developer.folk.app/api-reference/people/list-people /schemas/2025-06-09.json get /v1/people Retrieve a list of people in the workspace. # Update a person Source: https://developer.folk.app/api-reference/people/update-a-person patch /v1/people/{personId} Update an existing person in the workspace. Update an existing person in the workspace, updating only the provided fields and leaving the rest of the person's data unchanged. Fields with a list of values (`groups`, `companies`, `addresses`, `emails`, `phones`, `urls`) will **replace the old values**. This means that you must provide the entire list of values for that field, not just the values you want to add. When removing a person from a group, their custom field values for that group are also removed. # Update people in batch Source: https://developer.folk.app/api-reference/people/update-people-in-batch post /v1/people/batch/update Update up to 100 existing people in the workspace, each with the fields `PATCH /v1/people/{personId}` accepts. Each record is updated on its own: one that fails does not prevent the others from being updated. Fields with a list of values (`groups`, `companies`, `addresses`, `emails`, `phones`, `urls`) will **replace the old values**. This means that you must provide the entire list of values for that field, not just the values you want to add. **`groups` replaces the groups of the person, and removing a group deletes data.** A group you can access that is missing from `groups` is removed from the person, together with the person's custom field values in that group. This is the opposite of `POST /v1/people/batch/create`, where `groups` only adds: the same payload adds groups when it creates a person and removes groups when it updates one. Leave `groups` out to keep the person's groups unchanged. Each result lists the groups it removed in `removedGroups`. The records of one person, by its ID or by the ID of a person merged into it, are merged into one update, in request order: the last value given for a field wins, and `customFieldValues` are merged group by group, the last value given for a custom field winning. The merged update is validated as a whole: if it is invalid, every record of that person fails with that error. Each of those records gets its own result, with the person as finally updated. Update up to 100 existing people in the workspace, identified by their `id`. Each record takes the same fields as [update a person](/api-reference/people/update-a-person), native fields and `customFieldValues` alike, and is updated on its own: a record that fails, for instance because its `id` matches no person you can access, does not prevent the others from being updated. See [batch requests](/api-reference/batch-requests) for the 100-record limit and how to read the response. Fields with a list of values (`groups`, `companies`, `addresses`, `emails`, `phones`, `urls`) will **replace the old values**. This means that you must provide the entire list of values for that field, not just the values you want to add. **`groups` replaces the groups of the person, and removing a group deletes data.** A group you can access that is missing from `groups` is removed from the person, together with the person's custom field values in that group. Leave `groups` out to keep the person's groups unchanged. This is the opposite of [create people in batch](/api-reference/people/create-people-in-batch), where `groups` only adds. A script that creates a person when it has no `id` and updates it otherwise gets opposite results from the same payload: the create adds the listed groups, the update removes every other group. Each succeeded result lists, in `removedGroups`, the groups the update removed the person from, empty when it removed none. The records of one person, by its `id` or by the `id` of a person merged into it, are merged into one update, in the order you sent them: the last value given for a field wins, and `customFieldValues` are merged group by group, the last value given for a custom field winning. With `"firstName": "Foo"` then `"firstName": "Bar"`, the person ends up named `Bar`. The merged update is validated as a whole: if it is invalid, every record of that person fails with that error. Each of those records gets its own result, with the person as finally updated. Records naming the same new company, by `name`, are all linked to one created company. Names match exactly, so `Acme` and `acme` are two companies. Each updated person comes back in full, as in the response of [update a person](/api-reference/people/update-a-person). A record that was updated but not entirely, for instance because the person could not be added to one of its groups, succeeds with a `warnings` list explaining what was not applied. # Rate limits Source: https://developer.folk.app/api-reference/rate-limits folk's API uses rate limits to safeguard against bursts of incoming traffic and help maximise its stability. Rate limits are applied on a per-user basis. That means multiple API keys for the same user will share the same rate limit amount and reset window. ## Rate limit policies Every request counts against two policies at the same time. A request is rejected as soon as either of them is exhausted. | Policy | Limit | Window | Purpose | | - | - | - | - | | `default` | 600 requests | 1 minute | Caps your sustained throughput. | | `burst` | 20 requests | 2 seconds | Smooths out spikes, such as many parallel requests. | Windows are fixed and aligned to the clock: the `default` window resets at the start of every minute, and the `burst` window every 2 seconds. In practice, sending 600 requests at once gets rejected by the `burst` policy even though your per-minute quota is not used up. Spread your requests evenly (for example, at most 10 requests per second) to stay within both. Rejected requests count too: a request refused by one policy still consumes quota from the other. Retrying immediately after a `429` therefore eats into your per-minute quota, so always wait for the time given by `Retry-After`. We reserve the right to change these limits at any time. You should always rely on the response headers below rather than hard-coding them. ## Rate limit headers Every API response includes headers describing each policy and your current usage. ### Standard headers folk implements the [IETF RateLimit header fields](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/), which describe every policy in a single header: * `RateLimit-Policy`: The policies applied to the request. `q` is the number of requests allowed per window, and `w` is the window length in seconds. * `RateLimit`: Your current usage of each policy. `r` is the number of requests remaining, and `t` is the number of seconds until the window resets. ```http theme={null} RateLimit-Policy: "default";q=600;w=60, "burst";q=20;w=2 RateLimit: "default";r=598;t=42, "burst";r=18;t=1 ``` ### Legacy headers The following headers are also returned, one set per policy: | `default` policy | `burst` policy | Description | | - | - | - | | `X-RateLimit-Limit` | `X-RateLimit-Limit-Burst` | The maximum number of requests you can make in the window. | | `X-RateLimit-Remaining` | `X-RateLimit-Remaining-Burst` | The number of requests remaining in the current window. | | `X-RateLimit-Reset` | `X-RateLimit-Reset-Burst` | The time at which the window resets, in seconds since epoch. | ## Exceeding a rate limit If you exceed a rate limit, you will receive a `429` HTTP status code response and a [Rate limit error](/api-reference/errors#rate-limiting). The error's `details` tell you which policy was exceeded and the status of every policy.
A `Retry-After` header is also included in the response, indicating the number of seconds you should wait before making another request. When several policies are exceeded, it reflects the one that resets last. ## Handling limiting gracefully A basic technique for integrations to gracefully handle limiting is to watch for 429 status codes and build a retry mechanism using the `Retry-After` response header or the `details.retryAfter` property in the error response body. A rate-limited request is rejected before it runs, so it never has side effects and is always safe to retry. To also retry safely after a timeout or a network error, send an [`Idempotency-Key`](/api-reference/idempotency). The retry mechanism should follow an exponential backoff schedule to reduce request volume when necessary. We’d also recommend building some randomness into the backoff schedule to avoid a [thundering herd effect.](https://en.wikipedia.org/wiki/Thundering_herd_problem) To avoid hitting the limits in the first place, throttle your client using the `RateLimit` header: when `r` reaches `0` for a policy, wait `t` seconds before sending the next request. # Create a reminder Source: https://developer.folk.app/api-reference/reminders/create-a-reminder post /v1/reminders Create a new reminder in the workspace.
This endpoint is deprecated since August 13, 2026 and will be removed on February 13, 2027. Please use the Create a task endpoint instead. Create a reminder linked to a person, company or deal. ### Recurrence rule The recurrence rules format is based on the [iCalendar standard](https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html). However, we don't support the full spec, but only a subset of the features. The format of the recurrence must be the following: ``` DTSTART;TZID=:T