Skip to main content

Add new API clients

info

First, setup the repository tooling so that you can lint, test, and preview documentation on your computer.

To add a new API client in a supported programming language, you need to run these steps:

  1. Write specs
  2. Configure the generator
  3. Generate the client
  4. Add tests
  5. Helpers

For more information about adding support for a new programming language, see Support a new language.

1. Write specs

Algolia’s API specs follow the OpenAPI specification, version 3.1.

To add a new spec, it’s best to start from an existing one.

Don’t edit files in the specs/bundled/ directory. These files are auto-generated and your changes will be overwritten.

How to organize the specs

Each API spec follows a consistent structure.

specs/common/

The common directory contains schemas and parameters that are common to many Algolia APIs.

specs/<apiName>/

Each API is in its own directory.

For an example, see the Search API directory.

specs/<apiName>/spec.yml

The spec.yml file contains the general description of the API, including the servers and paths properties.

To get started, copy an existing file, for example, from the Search API.

For information about documenting the API, see API descriptions.

specs/<apiName>/common/

This directory contains schemas and parameters that are common to endpoints of the current API. For schemas that are shared between multiple APIs, see the global specs/common directory.

specs/<apiName>/paths/

This directory contains the descriptions of the endpoints of the API. The paths themselves are defined in the spec.yml file.

Guidelines for operations

Operations are endpoints combined with an HTTP method (GET, POST, etc.). Each operation must have a unique operationID property. Operations for the same endpoint may share the same file, for example, the GET and DELETE request for the same endpoint.

Every operation must have a summary and description property. For information about documenting operations, see Operation summaries and Operation descriptions.

Filenames

Follow these conventions:

  • If the file only contains one operation, use <operationId>.yml as filename.
  • If the file contains multiple operations, use a more generic name, for example rule.yml for the GET, PUT, and DELETE request for a rule.

Access control lists (ACL)

Each operation should include an x-acl property to document the ACL required to make the request.

The x-acl property is an array of strings. For operations that require the admin API key, use admin.

Complex objects

The following objects must not be inlined, but referenced with $ref:

  • Nested arrays
  • Nested objects
  • oneOf
  • allOf
  • enum

This is required for accurate naming of the generated code objects. It also improves the readability of the specs.

Guidelines for properties and parameters

  • Create separate objects and reference them for complex objects.

  • The format parameter for strings isn’t supported.

  • For nullable properties, use the following syntax:

    nullableProp:
    default: null
    oneOf:
    - type: string
    description: Some value
    - type: 'null'
    description: The single quotes are required.

For information about documenting properties and parameters, see Properties and parameters.

Troubleshooting

Explicit names for request bodies

Detailed issue

In some cases, the generated name for the requestBody property might be wrong. This can happen in these cases:

To provide a name for the request body, add the x-codegen-request-body-name property to the root of the operation’s spec file.

For an example, see pull request #896.

Send additional options to the templates

To send additional information to the generators, you can add properties starting with x- to the root level of your spec. These are available in the templates as part of the vendorExtensions object.

For an example, see search.yml

2. Configure the generator

Most of the configuration is “guessed” by the api-clients-automation CLI (scripts/).

Configuration file

The file config/clients.config.json contains information that’s common to all clients generated for each language.

The following fields are required:

OptionDescription
clientsThe clients to generate, either a list of string (matching the api name), or see below
clients.nameThe name of the client to generate (matching the API name)
clients.outputThe output folder
folderPath to the parent folder of every client for this language.
gitRepoIdName of the repository for this API client.
packageVersionInitial version number to publish for the generated client. It will be automatically incremented.
modelFolderPath to the model folder that will host the generated code.
apiFolderPath to the api folder that will host the generated code.
dockerImageThe name of the docker image that runs this client apic_base
tests.extensionTest file extension, such as .test.java or _test.py
tests.outputFolderPath to the folder that holds the tests for this language, such as tests/
snippets.extensionSnippet file extension, such as .java or .py
snippets.outputFolderPath to the folder that holds the snippets for this language, such as lib/

3. Generate the client

Use the CLI to generate the clients:

4. Add tests with the Common Test Suite

You must add tests for your clients. For more information, see Common Test Suite.

5. Helpers

The API clients have hand-written helpers for tasks that would otherwise require custom code.

Helper nameDescriptionWrapped API callStop conditionExample
waitForTaskGiven a taskID, calls the getTask method until the status gets publishedgetTask()response.status == "published"JavaScript
waitForAppTaskGiven a taskID, calls the getAppTask method until the status gets publishedgetAppTask()response.status == "published"JavaScript
waitForApiKeyGiven a Key, calls the getApiKey method until the stop condition for the given operation is validatedgetApiKey()Diff between the given Key and the response payloadJavaScript
browseObjects<T>Given an indexName and the same parameters as the browse method, aggregates all the objects returned by the API calls in a single browse response objectbrowse()response.cursor == nullJavaScript
browseRulesGiven an indexName and the same parameters as the searchRules method, aggregates all the rules returned by the API calls in a single searchRules response objectsearchRules()response.nbHits < params.hitsPerPageJavaScript
browseSynonymsGiven an indexName and the same parameters as the searchSynonyms method, aggregates all the synonyms returned by the API calls in a single searchSynonyms response objectsearchSynonyms()response.nbHits < params.hitsPerPageJavaScript
searchForHits<T>Given the same parameters as the search method, returns the API response with the certainty that it will only contain hits by casting it to a generic SearchResponse<T> objectsearch()noneJavaScript
searchForFacetsGiven the same parameters as the search method, returns the API response with the certainty that it will only contain facets by casting it to a SearchForFacetValuesResponse objectsearch()noneJavaScript
replaceAllObjectsGiven an indexName and an array of objects, replace all objects in this index using a temporary oneoperationIndex(), batch()nonePHP
generateSecuredApiKeyGiven an indexName and an array of restrictions, generates a secured API Key using the SHA-256 algorithmnonenonePHP
chunkedBatchCreates chunks of objects in order to make them fit in multiple batch requestsbatch()nonePHP
saveObjectsSaves the given array of objects in the given index. The chunkedBatch helper is used under the hood, which creates a batch requests with at most 1000 objects in it.batch()noneGo
deleteObjectsDeletes every records for the given objectIDs. The chunkedBatch helper is used under the hood, which creates a batch requests with at most 1000 objectIDs in it.batch()noneGo
partialUpdateObjectsReplaces object content of all the given objects according to their respective objectID field. The chunkedBatch helper is used under the hood, which creates a batch requests with at most 1000 objects in it.batch()noneGo

replaceAllObjects

info

This section explains the decision over the implementation of the helper, as it’s quite tricky the get the order right

caution

Always wait on the tmp index, because:

  • it’s the only index that we know for sure will exist (since we create it).
  • it is Metis compliant, no need for custom implementation.

1. copy index

The first step is to make a copy of the index settings, rules and synonyms.

We don’t wait for this first operation, as we:

  • don’t know if the source index exists, and therefore can’t call waitTask on it.
  • won’t have a tmp index to waitTask on, if the copy can’t succeed.

2. chunk batch

Call the chunk batch for the given objects, call waitTask for the batching operation, which would create the tmp index in case copy did not succeded.

3. copy index again

Now that we are certain the tmp index exist:

  1. waitTask on the tmp index, with the taskID returned from the first copy index invocation.
  2. call copy again, if the first call succeeded, it will be a noop, otherwise, the tmp index will be rebuilt.
  3. waitTask on the tmp index

4. move tmp index

Move the tmp index to the source index, call waitTask on the tmp index.