> ## Documentation Index
> Fetch the complete documentation index at: https://jorgecastro.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# List authors



## OpenAPI

````yaml /openapi/contract.yaml get /authors
openapi: 3.1.0
info:
  title: Custom Integration contract (v1)
  version: '1.0'
  summary: The REST endpoints you implement so Jorge Castro can publish to your site.
  description: >
    <div style="padding:12px 16px;border-left:3px solid
    #9827FC;background:rgba(152,39,252,0.06);border-radius:0 8px 8px
    0;margin-bottom:16px">

    <strong>Read this backwards.</strong> Jorge Castro is the <em>client</em>
    here —

    <strong>you implement these endpoints on your own server</strong>. Nothing
    below runs on

    our infrastructure.

    </div>


    When a user clicks Publish, Update or Delete in Jorge Castro, our servers
    send

    signed HTTPS requests to the base URL you registered (for example

    `https://your-site.com/api/castro`) using the paths below. Your
    implementation

    stores the content wherever your site reads it from, and returns the ids

    described here.


    ## Only three endpoints are required


    `POST /handshake`, `POST /posts` and `PUT /posts/{id}`. Everything else is

    optional: declare what you built in the handshake's `capabilities` array,
    and

    Castro only ever calls what you declared. See

    [Capabilities](/capabilities).


    ## Two rules that govern every endpoint


    **1. `PUT` is a partial update.** Only the fields present in the body
    change;

    omitted fields keep their current values. Castro's *Update Content* action

    sends nothing but `content` — treating that as a full replace erases the

    user's title, categories and SEO. Create and update therefore have

    **different schemas** below (`PostCreate` vs `PostUpdate`), and the
    difference

    is the whole contract.


    **2. Every request is signed.** Three headers travel with each one; verify
    the

    HMAC over the **raw** request bytes, never over a re-serialized copy. See

    [Authentication](/authentication).


    ## Before you connect


    Run the [conformance script](/testing) against your endpoints. It asserts
    both

    rules above, plus that a bad signature is actually rejected.
  contact:
    name: Jorge Castro
    url: https://jorgecastro.ai
servers:
  - url: https://your-site.com/{basePath}
    description: Your server — the base URL you enter in Jorge Castro
    variables:
      basePath:
        default: api/castro
        description: Any prefix you like. Castro appends the paths below to it.
security:
  - apiKey: []
    castroTimestamp: []
    castroSignature: []
tags:
  - name: Handshake
    description: Required — connection verification and capability discovery.
  - name: Blog posts
    description: Create and update are required; delete is optional (`posts.delete`).
  - name: Blog categories
    description: Optional — capability `blog_categories`.
  - name: Authors
    description: Optional — capability `authors`.
  - name: Products
    description: Optional — capability `products`. Copy only (no price/SKU/stock).
  - name: Product categories
    description: Optional — capability `product_categories`.
  - name: SEO
    description: Optional — capability `seo`.
  - name: Pages
    description: Optional — capability `pages.list`.
paths:
  /authors:
    get:
      tags:
        - Authors
      summary: List authors
      operationId: listAuthors
      parameters:
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/Page'
        - name: search
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Author list (bare JSON array)
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Author'
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  parameters:
    PerPage:
      name: per_page
      in: query
      description: Items per page. Castro asks for 100.
      schema:
        type: integer
        default: 100
    Page:
      name: page
      in: query
      description: |
        1-based page number. Castro keeps paging until it receives a page
        **shorter** than `per_page`, so a full final page must be followed by an
        empty one — otherwise it will keep asking.
      schema:
        type: integer
        default: 1
  schemas:
    Author:
      type: object
      required:
        - id
      properties:
        id:
          type: string
        username:
          type: string
        email:
          type: string
        display_name:
          type: string
        first_name:
          type: string
        last_name:
          type: string
        description:
          type: string
        url:
          type: string
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: |
            Human-readable message, surfaced to the Castro user as-is. Make it
            actionable — "Post not found" is useful, "Error" is not.
          example: Post not found
  responses:
    Unauthorized:
      description: |
        Authentication failed — bad API key, bad signature, or a stale
        timestamp. Castro surfaces this as a connection problem and does not
        retry.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            badSignature:
              summary: Signature mismatch
              value:
                error: Invalid signature
            stale:
              summary: Timestamp too old
              value:
                error: Stale request
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        The connection key the user generated in Jorge Castro
        (Settings → Integration → Custom Website). Reject any request whose key
        doesn't match yours.
    castroTimestamp:
      type: apiKey
      in: header
      name: X-Castro-Timestamp
      description: |
        Unix time in **milliseconds** when the request was signed — Castro uses
        `Date.now()`. Reject anything more than a few minutes old.

        A seconds-based comparison makes every request look ~55,000 years in the
        future, and the freshness check then silently passes everything.
    castroSignature:
      type: apiKey
      in: header
      name: X-Castro-Signature
      description: |
        `HMAC-SHA256("{timestamp}.{rawBody}", api_key)`, lowercase hex.

        `rawBody` is the **exact bytes** of the request body — empty for `GET`
        and `DELETE`, which therefore sign over `"{timestamp}."`. Hash the raw
        bytes, never a re-serialized copy of the parsed JSON: whitespace and key
        order differences will break the comparison for some payloads and not
        others, which reads as an intermittent bug.

        Compare in constant time (`crypto.timingSafeEqual`, `hash_equals`,
        `hmac.compare_digest`).

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.